1# -*- perl -*- 2# 3# File::NFSLock - bdpO - NFS compatible (safe) locking utility 4# 5# $Id: NFSLock.pm,v 1.29 2018/11/01 14:00:00 bbb Exp $ 6# 7# Copyright (C) 2002, Paul T Seamons 8# paul@seamons.com 9# http://seamons.com/ 10# 11# Rob B Brown 12# bbb@cpan.org 13# 14# This package may be distributed under the terms of either the 15# GNU General Public License 16# or the 17# Perl Artistic License 18# 19# All rights reserved. 20# 21# Please read the perldoc File::NFSLock 22# 23################################################################ 24 25package File::NFSLock; 26 27use strict; 28use warnings; 29 30use Carp qw(croak confess); 31our $errstr; 32use base 'Exporter'; 33our @EXPORT_OK = qw(uncache); 34 35our $VERSION = '1.29'; 36 37#Get constants, but without the bloat of 38#use Fcntl qw(LOCK_SH LOCK_EX LOCK_NB); 39use constant { 40 LOCK_SH => 1, 41 LOCK_EX => 2, 42 LOCK_NB => 4, 43}; 44 45### Convert lock_type to a number 46our $TYPES = { 47 BLOCKING => LOCK_EX, 48 BL => LOCK_EX, 49 EXCLUSIVE => LOCK_EX, 50 EX => LOCK_EX, 51 NONBLOCKING => LOCK_EX | LOCK_NB, 52 NB => LOCK_EX | LOCK_NB, 53 SHARED => LOCK_SH, 54 SH => LOCK_SH, 55}; 56our $LOCK_EXTENSION = '.NFSLock'; # customizable extension 57our $HOSTNAME = undef; 58our $SHARE_BIT = 1; 59 60###----------------------------------------------------------------### 61 62my $graceful_sig = sub { 63 print STDERR "Received SIG$_[0]\n" if @_; 64 # Perl's exit should safely DESTROY any objects 65 # still "alive" before calling the real _exit(). 66 exit 1; 67}; 68 69our @CATCH_SIGS = qw(TERM INT); 70 71sub new { 72 $errstr = undef; 73 74 my $type = shift; 75 my $class = ref($type) || $type || __PACKAGE__; 76 my $self = {}; 77 78 ### allow for arguments by hash ref or serially 79 if( @_ && ref $_[0] ){ 80 $self = shift; 81 }else{ 82 $self->{file} = shift; 83 $self->{lock_type} = shift; 84 $self->{blocking_timeout} = shift; 85 $self->{stale_lock_timeout} = shift; 86 } 87 $self->{file} ||= ""; 88 $self->{lock_type} ||= 0; 89 $self->{blocking_timeout} ||= 0; 90 $self->{stale_lock_timeout} ||= 0; 91 $self->{lock_pid} = $$; 92 $self->{unlocked} = 1; 93 foreach my $signal (@CATCH_SIGS) { 94 if (!$SIG{$signal} || 95 $SIG{$signal} eq "DEFAULT") { 96 $SIG{$signal} = $graceful_sig; 97 } 98 } 99 100 ### force lock_type to be numerical 101 if( $self->{lock_type} && 102 $self->{lock_type} !~ /^\d+/ && 103 exists $TYPES->{$self->{lock_type}} ){ 104 $self->{lock_type} = $TYPES->{$self->{lock_type}}; 105 } 106 107 ### need the hostname 108 if( !$HOSTNAME ){ 109 require Sys::Hostname; 110 $HOSTNAME = Sys::Hostname::hostname(); 111 } 112 113 ### quick usage check 114 croak ($errstr = "Usage: my \$f = $class->new('/pathtofile/file',\n" 115 ."'BLOCKING|EXCLUSIVE|NONBLOCKING|SHARED', [blocking_timeout, stale_lock_timeout]);\n" 116 ."(You passed \"$self->{file}\" and \"$self->{lock_type}\")") 117 unless length($self->{file}); 118 119 croak ($errstr = "Unrecognized lock_type operation setting [$self->{lock_type}]") 120 unless $self->{lock_type} && $self->{lock_type} =~ /^\d+$/; 121 122 ### Input syntax checking passed, ready to bless 123 bless $self, $class; 124 125 ### choose a random filename 126 $self->{rand_file} = rand_file( $self->{file} ); 127 128 ### choose the lock filename 129 $self->{lock_file} = $self->{file} . $LOCK_EXTENSION; 130 131 my $quit_time = $self->{blocking_timeout} && 132 !($self->{lock_type} & LOCK_NB) ? 133 time() + $self->{blocking_timeout} : 0; 134 135 ### remove an old lockfile if it is older than the stale_timeout 136 if( -e $self->{lock_file} && 137 $self->{stale_lock_timeout} > 0 && 138 time() - (stat _)[9] > $self->{stale_lock_timeout} ){ 139 unlink $self->{lock_file}; 140 } 141 142 while (1) { 143 ### open the temporary file 144 $self->create_magic 145 or return undef; 146 147 if ( $self->{lock_type} & LOCK_EX ) { 148 last if $self->do_lock; 149 } elsif ( $self->{lock_type} & LOCK_SH ) { 150 last if $self->do_lock_shared; 151 } else { 152 $errstr = "Unknown lock_type [$self->{lock_type}]"; 153 return undef; 154 } 155 156 ### Lock failed! 157 158 ### I know this may be a race condition, but it's okay. It is just a 159 ### stab in the dark to possibly find long dead processes. 160 161 ### If lock exists and is readable, see who is mooching on the lock 162 163 my $fh; 164 if ( -e $self->{lock_file} && 165 open ($fh,'+<', $self->{lock_file}) ){ 166 167 my @mine = (); 168 my @them = (); 169 my @dead = (); 170 171 my $has_lock_exclusive = !((stat _)[2] & $SHARE_BIT); 172 my $try_lock_exclusive = !($self->{lock_type} & LOCK_SH); 173 174 while(defined(my $line=<$fh>)){ 175 if ($line =~ /^\Q$HOSTNAME\E (-?\d+) /) { 176 my $pid = $1; 177 if ($pid == $$) { # This is me. 178 push @mine, $line; 179 }elsif(kill 0, $pid) { # Still running on this host. 180 push @them, $line; 181 }else{ # Finished running on this host. 182 push @dead, $line; 183 } 184 } else { # Running on another host, so 185 push @them, $line; # assume it is still running. 186 } 187 } 188 189 ### If there was at least one stale lock discovered... 190 if (@dead) { 191 # Lock lock_file to avoid a race condition. 192 local $LOCK_EXTENSION = ".shared"; 193 my $lock = new File::NFSLock { 194 file => $self->{lock_file}, 195 lock_type => LOCK_EX, 196 blocking_timeout => 62, 197 stale_lock_timeout => 60, 198 }; 199 200 ### Rescan in case lock contents were modified between time stale lock 201 ### was discovered and lockfile lock was acquired. 202 seek ($fh, 0, 0); 203 my $content = ''; 204 while(defined(my $line=<$fh>)){ 205 if ($line =~ /^\Q$HOSTNAME\E (-?\d+) /) { 206 my $pid = $1; 207 next if (!kill 0, $pid); # Skip dead locks from this host 208 } 209 $content .= $line; # Save valid locks 210 } 211 212 ### Save any valid locks or wipe file. 213 if( length($content) ){ 214 seek $fh, 0, 0; 215 print $fh $content; 216 truncate $fh, length($content); 217 close $fh; 218 }else{ 219 close $fh; 220 unlink $self->{lock_file}; 221 } 222 223 ### No "dead" or stale locks found. 224 } else { 225 close $fh; 226 } 227 228 ### If attempting to acquire the same type of lock 229 ### that it is already locked with, and I've already 230 ### locked it myself, then it is safe to lock again. 231 ### Just kick out successfully without really locking. 232 ### Assumes locks will be released in the reverse 233 ### order from how they were established. 234 if ($try_lock_exclusive eq $has_lock_exclusive && @mine){ 235 return $self; 236 } 237 } 238 239 ### If non-blocking, then kick out now. 240 ### ($errstr might already be set to the reason.) 241 if ($self->{lock_type} & LOCK_NB) { 242 $errstr ||= "NONBLOCKING lock failed!"; 243 return undef; 244 } 245 246 ### wait a moment 247 sleep(1); 248 249 ### but don't wait past the time out 250 if( $quit_time && (time > $quit_time) ){ 251 $errstr = "Timed out waiting for blocking lock"; 252 return undef; 253 } 254 255 # BLOCKING Lock, So Keep Trying 256 } 257 258 ### clear up the NFS cache 259 $self->uncache; 260 261 ### Yes, the lock has been acquired. 262 delete $self->{unlocked}; 263 264 return $self; 265} 266 267sub DESTROY { 268 shift()->unlock(); 269} 270 271sub unlock ($) { 272 my $self = shift; 273 if (!$self->{unlocked}) { 274 unlink( $self->{rand_file} ) if -e $self->{rand_file}; 275 if( $self->{lock_type} & LOCK_SH ){ 276 $self->do_unlock_shared; 277 }else{ 278 $self->do_unlock; 279 } 280 $self->{unlocked} = 1; 281 foreach my $signal (@CATCH_SIGS) { 282 if ($SIG{$signal} && 283 ($SIG{$signal} eq $graceful_sig)) { 284 # Revert handler back to how it used to be. 285 # Unfortunately, this will restore the 286 # handler back even if there are other 287 # locks still in tact, but for most cases, 288 # it will still be an improvement. 289 delete $SIG{$signal}; 290 } 291 } 292 } 293 return 1; 294} 295 296###----------------------------------------------------------------### 297 298# concepts for these routines were taken from Mail::Box which 299# took the concepts from Mail::Folder 300 301 302sub rand_file ($) { 303 my $file = shift; 304 "$file.tmp.". time()%10000 .'.'. $$ .'.'. int(rand()*10000); 305} 306 307sub create_magic ($;$) { 308 $errstr = undef; 309 my $self = shift; 310 my $append_file = shift || $self->{rand_file}; 311 $self->{lock_line} ||= "$HOSTNAME $self->{lock_pid} ".time()." ".int(rand()*10000)."\n"; 312 open (my $fh,'>>', $append_file) or do { $errstr = "Couldn't open \"$append_file\" [$!]"; return undef; }; 313 print $fh $self->{lock_line}; 314 close $fh; 315 return 1; 316} 317 318sub do_lock { 319 $errstr = undef; 320 my $self = shift; 321 my $lock_file = $self->{lock_file}; 322 my $rand_file = $self->{rand_file}; 323 my $chmod = 0600; 324 chmod( $chmod, $rand_file) 325 || die "I need ability to chmod files to adequatetly perform locking"; 326 327 ### try a hard link, if it worked 328 ### two files are pointing to $rand_file 329 my $success = link( $rand_file, $lock_file ) 330 && -e $rand_file && (stat _)[3] == 2; 331 unlink $rand_file; 332 333 return $success; 334} 335 336sub do_lock_shared { 337 $errstr = undef; 338 my $self = shift; 339 my $lock_file = $self->{lock_file}; 340 my $rand_file = $self->{rand_file}; 341 342 ### chmod local file to make sure we know before 343 my $chmod = 0600; 344 $chmod |= $SHARE_BIT; 345 chmod( $chmod, $rand_file) 346 || die "I need ability to chmod files to adequatetly perform locking"; 347 348 ### lock the locking process 349 local $LOCK_EXTENSION = ".shared"; 350 my $lock = new File::NFSLock { 351 file => $lock_file, 352 lock_type => LOCK_EX, 353 blocking_timeout => 62, 354 stale_lock_timeout => 60, 355 }; 356 # The ".shared" lock will be released as this status 357 # is returned, whether or not the status is successful. 358 359 ### If I didn't have exclusive and the shared bit is not 360 ### set, I have failed 361 362 ### Try to create $lock_file from the special 363 ### file with the magic $SHARE_BIT set. 364 my $success = link( $rand_file, $lock_file); 365 unlink $rand_file; 366 if ( !$success && 367 -e $lock_file && 368 ((stat _)[2] & $SHARE_BIT) != $SHARE_BIT ){ 369 370 $errstr = 'Exclusive lock exists.'; 371 return undef; 372 373 } elsif ( !$success ) { 374 ### Shared lock exists, append my lock 375 $self->create_magic ($self->{lock_file}); 376 } 377 378 # Success 379 return 1; 380} 381 382sub do_unlock ($) { 383 return unlink shift->{lock_file}; 384} 385 386sub do_unlock_shared ($) { 387 $errstr = undef; 388 my $self = shift; 389 my $lock_file = $self->{lock_file}; 390 my $lock_line = $self->{lock_line}; 391 392 ### lock the locking process 393 local $LOCK_EXTENSION = '.shared'; 394 my $lock = new File::NFSLock ($lock_file,LOCK_EX,62,60); 395 396 ### get the handle on the lock file 397 my $fh; 398 if( ! open ($fh,'+<', $lock_file) ){ 399 if( ! -e $lock_file ){ 400 return 1; 401 }else{ 402 die "Could not open for writing shared lock file $lock_file ($!)"; 403 } 404 } 405 406 ### read existing file 407 my $content = ''; 408 while(defined(my $line=<$fh>)){ 409 next if $line eq $lock_line; 410 $content .= $line; 411 } 412 413 ### other shared locks exist 414 if( length($content) ){ 415 seek $fh, 0, 0; 416 print $fh $content; 417 truncate $fh, length($content); 418 close $fh; 419 420 ### only I exist 421 }else{ 422 close $fh; 423 unlink $lock_file; 424 } 425 426} 427 428sub uncache ($;$) { 429 # allow as method call 430 my $file = pop; 431 ref $file && ($file = $file->{file}); 432 my $rand_file = rand_file( $file ); 433 434 ### hard link to the actual file which will bring it up to date 435 return ( link( $file, $rand_file) && unlink($rand_file) ); 436} 437 438sub newpid { 439 my $self = shift; 440 # Detect if this is the parent or the child 441 if ($self->{lock_pid} == $$) { 442 # This is the parent 443 444 # Must wait for child to call newpid before processing. 445 # A little patience for the child to call newpid 446 my $patience = time + 10; 447 while (time < $patience) { 448 if (rename("$self->{lock_file}.fork",$self->{rand_file})) { 449 # Child finished its newpid call. 450 # Wipe the signal file. 451 unlink $self->{rand_file}; 452 last; 453 } 454 # Brief pause before checking again 455 # to avoid intensive IO across NFS. 456 select(undef,undef,undef,0.1); 457 } 458 459 # Child finished running newpid() and acquired shared lock 460 # So now we're safe to continue without risk of 461 # blowing away the lock prematurely. 462 unless ( $self->{lock_type} & LOCK_SH ) { 463 # If it's not already a SHared lock, then 464 # just switch it from EXclusive to SHared 465 # from this process's point of view. 466 # Then the child will still hold the lock 467 # if the parent releases it first. 468 # (Don't chmod the lock file.) 469 $self->{lock_type} |= LOCK_SH; 470 } 471 } else { 472 # This is the new child 473 474 # Fix lock_pid to the new pid. 475 $self->{lock_pid} = $$; 476 477 # We can leave the old lock_line in the lock_file 478 # But we need to add the new lock_line for this pid. 479 480 # Clear lock_line to create a fresh one. 481 delete $self->{lock_line}; 482 # Append a new lock_line to the lock_file. 483 $self->create_magic($self->{lock_file}); 484 485 unless ( $self->{lock_type} & LOCK_SH ) { 486 # If it's not already a SHared lock, then 487 # just switch it from EXclusive to SHared 488 # from this process's point of view. 489 # Then the parent will still hold the lock 490 # if this child releases it first. 491 # (Don't chmod the lock file.) 492 $self->{lock_type} |= LOCK_SH; 493 } 494 495 # Create signal file to notify parent that 496 # the lock_line entry has been delegated. 497 open (my $fh, '>', "$self->{lock_file}.fork"); 498 close($fh); 499 } 500} 501 502sub fork { 503 my $self = shift; 504 # Store fork response. 505 my $pid = CORE::fork(); 506 if (defined $pid and !$self->{unlocked}) { 507 # Fork worked and we really have a lock to deal with 508 # So upgrade to shared lock across both parent and child 509 $self->newpid; 510 } 511 # Return original fork response 512 return $pid; 513} 514 5151; 516 517 518=pod 519 520=head1 NAME 521 522File::NFSLock - perl module to do NFS (or not) locking 523 524=head1 SYNOPSIS 525 526 use File::NFSLock qw(uncache); 527 use Fcntl qw(LOCK_EX LOCK_NB); 528 529 my $file = "somefile"; 530 531 ### set up a lock - lasts until object looses scope 532 if (my $lock = new File::NFSLock { 533 file => $file, 534 lock_type => LOCK_EX|LOCK_NB, 535 blocking_timeout => 10, # 10 sec 536 stale_lock_timeout => 30 * 60, # 30 min 537 }) { 538 539 ### OR 540 ### my $lock = File::NFSLock->new($file,LOCK_EX|LOCK_NB,10,30*60); 541 542 ### do write protected stuff on $file 543 ### at this point $file is uncached from NFS (most recent) 544 open(FILE, "+<$file") || die $!; 545 546 ### or open it any way you like 547 ### my $fh = IO::File->open( $file, 'w' ) || die $! 548 549 ### update (uncache across NFS) other files 550 uncache("someotherfile1"); 551 uncache("someotherfile2"); 552 # open(FILE2,"someotherfile1"); 553 554 ### unlock it 555 $lock->unlock(); 556 ### OR 557 ### undef $lock; 558 ### OR let $lock go out of scope 559 }else{ 560 die "I couldn't lock the file [$File::NFSLock::errstr]"; 561 } 562 563 564=head1 DESCRIPTION 565 566Program based of concept of hard linking of files being atomic across 567NFS. This concept was mentioned in Mail::Box::Locker (which was 568originally presented in Mail::Folder::Maildir). Some routine flow is 569taken from there -- particularly the idea of creating a random local 570file, hard linking a common file to the local file, and then checking 571the nlink status. Some ideologies were not complete (uncache 572mechanism, shared locking) and some coding was even incorrect (wrong 573stat index). File::NFSLock was written to be light, generic, 574and fast. 575 576 577=head1 USAGE 578 579Locking occurs by creating a File::NFSLock object. If the object 580is created successfully, a lock is currently in place and remains in 581place until the lock object goes out of scope (or calls the unlock 582method). 583 584A lock object is created by calling the new method and passing two 585to four parameters in the following manner: 586 587 my $lock = File::NFSLock->new($file, 588 $lock_type, 589 $blocking_timeout, 590 $stale_lock_timeout, 591 ); 592 593Additionally, parameters may be passed as a hashref: 594 595 my $lock = File::NFSLock->new({ 596 file => $file, 597 lock_type => $lock_type, 598 blocking_timeout => $blocking_timeout, 599 stale_lock_timeout => $stale_lock_timeout, 600 }); 601 602=head1 PARAMETERS 603 604=over 4 605 606=item Parameter 1: file 607 608Filename of the file upon which it is anticipated that a write will 609happen to. Locking will provide the most recent version (uncached) 610of this file upon a successful file lock. It is not necessary 611for this file to exist. 612 613=item Parameter 2: lock_type 614 615Lock type must be one of the following: 616 617 BLOCKING 618 BL 619 EXCLUSIVE (BLOCKING) 620 EX 621 NONBLOCKING 622 NB 623 SHARED 624 SH 625 626Or else one or more of the following joined with '|': 627 628 Fcntl::LOCK_EX() (BLOCKING) 629 Fcntl::LOCK_NB() (NONBLOCKING) 630 Fcntl::LOCK_SH() (SHARED) 631 632Lock type determines whether the lock will be blocking, non blocking, 633or shared. Blocking locks will wait until other locks are removed 634before the process continues. Non blocking locks will return undef if 635another process currently has the lock. Shared will allow other 636process to do a shared lock at the same time as long as there is not 637already an exclusive lock obtained. 638 639=item Parameter 3: blocking_timeout (optional) 640 641Timeout is used in conjunction with a blocking timeout. If specified, 642File::NFSLock will block up to the number of seconds specified in 643timeout before returning undef (could not get a lock). 644 645 646=item Parameter 4: stale_lock_timeout (optional) 647 648Timeout is used to see if an existing lock file is older than the stale 649lock timeout. If do_lock fails to get a lock, the modified time is checked 650and do_lock is attempted again. If the stale_lock_timeout is set to low, a 651recursion load could exist so do_lock will only recurse 10 times (this is only 652a problem if the stale_lock_timeout is set too low -- on the order of one or two 653seconds). 654 655=back 656 657=head1 METHODS 658 659After the $lock object is instantiated with new, 660as outlined above, some methods may be used for 661additional functionality. 662 663=head2 unlock 664 665 $lock->unlock; 666 667This method may be used to explicitly release a lock 668that is acquired. In most cases, it is not necessary 669to call unlock directly since it will implicitly be 670called when the object leaves whatever scope it is in. 671 672=head2 uncache 673 674 $lock->uncache; 675 $lock->uncache("otherfile1"); 676 uncache("otherfile2"); 677 678This method is used to freshen up the contents of a 679file across NFS, ignoring what is contained in the 680NFS client cache. It is always called from within 681the new constructor on the file that the lock is 682being attempted. uncache may be used as either an 683object method or as a stand alone subroutine. 684 685=head2 fork 686 687 my $pid = $lock->fork; 688 if (!defined $pid) { 689 # Fork Failed 690 } elsif ($pid) { 691 # Parent ... 692 } else { 693 # Child ... 694 } 695 696fork() is a convenience method that acts just like the normal 697CORE::fork() except it safely ensures the lock is retained 698within both parent and child processes. WITHOUT this, then when 699either the parent or child process releases the lock, then the 700entire lock will be lost, allowing external processes to 701re-acquire a lock on the same file, even if the other process 702still has the lock object in scope. This can cause corruption 703since both processes might think they have exclusive access to 704the file. 705 706=head2 newpid 707 708 my $pid = fork; 709 if (!defined $pid) { 710 # Fork Failed 711 } elsif ($pid) { 712 $lock->newpid; 713 # Parent ... 714 } else { 715 $lock->newpid; 716 # Child ... 717 } 718 719The newpid() synopsis shown above is equivalent to the 720one used for the fork() method, but it's not intended 721to be called directly. It is called internally by the 722fork() method. To be safe, it is recommended to use 723$lock->fork() from now on. 724 725=head1 FAILURE 726 727On failure, a global variable, $File::NFSLock::errstr, should be set and should 728contain the cause for the failure to get a lock. Useful primarily for debugging. 729 730=head1 LOCK_EXTENSION 731 732By default File::NFSLock will use a lock file extension of ".NFSLock". This is 733in a global variable $File::NFSLock::LOCK_EXTENSION that may be changed to 734suit other purposes (such as compatibility in mail systems). 735 736=head1 REPO 737 738The source is now on github: 739 740git clone https://github.com/hookbot/File-NFSLock 741 742=head1 BUGS 743 744If you spot anything, please submit a pull request on 745github and/or submit a ticket with RT: 746https://rt.cpan.org/Dist/Display.html?Queue=File-NFSLock 747 748=head2 FIFO 749 750Locks are not necessarily obtained on a first come first serve basis. 751Not only does this not seem fair to new processes trying to obtain a lock, 752but it may cause a process starvation condition on heavily locked files. 753 754=head2 DIRECTORIES 755 756Locks cannot be obtained on directory nodes, nor can a directory node be 757uncached with the uncache routine because hard links do not work with 758directory nodes. Some other algorithm might be used to uncache a 759directory, but I am unaware of the best way to do it. The biggest use I 760can see would be to avoid NFS cache of directory modified and last accessed 761timestamps. 762 763=head1 INSTALL 764 765Download and extract tarball before running 766these commands in its base directory: 767 768 perl Makefile.PL 769 make 770 make test 771 make install 772 773For RPM installation, download tarball before 774running these commands in your _topdir: 775 776 rpm -ta SOURCES/File-NFSLock-*.tar.gz 777 rpm -ih RPMS/noarch/perl-File-NFSLock-*.rpm 778 779=head1 AUTHORS 780 781Paul T Seamons (paul@seamons.com) - Performed majority of the 782programming with copious amounts of input from Rob Brown. 783 784Rob B Brown (bbb@cpan.org) - In addition to helping in the 785programming, Rob Brown provided most of the core testing to make sure 786implementation worked properly. He is now the current maintainer. 787 788Also Mark Overmeer (mark@overmeer.net) - Author of Mail::Box::Locker, 789from which some key concepts for File::NFSLock were taken. 790 791Also Kevin Johnson (kjj@pobox.com) - Author of Mail::Folder::Maildir, 792from which Mark Overmeer based Mail::Box::Locker. 793 794=head1 COPYRIGHT 795 796 Copyright (C) 2001 797 Paul T Seamons 798 paul@seamons.com 799 http://seamons.com/ 800 801 Copyright (C) 2002-2018, 802 Rob B Brown 803 bbb@cpan.org 804 805 This package may be distributed under the terms of either the 806 GNU General Public License 807 or the 808 Perl Artistic License 809 810 All rights reserved. 811 812=cut 813