File: //proc/self/root/proc/self/root/scripts.20110531.215904.25158/AcctLock.pm
package AcctLock;
# cpanel - AcctLock.pm Copyright(c) 1999-2008 cPanel, Inc.
# All rights Reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
use strict;
use Fcntl ();
use cPScript::Logger ();
use cPScript::WHMAPI ();
my $logger = cPScript::Logger->new();
# Accounting lock file location, not used or specified anywhere else
my $accounting_lock_file = '/var/cpanel/acct.lock';
my $lock_expire_seconds = 100; # Wait time for lock expiration before stealing lock
my $loop_check = 0; # Internal counter to prevent deep recursion
#
# is it currently locked?
# externally no need to know
# the exact name of the file
#
sub is_acctlock {
return 1 if (-e $accounting_lock_file);
return 0;
} # end of is_acctlock
sub acctlock {
$loop_check++;
# Terminate recursion if already failed 10 times
# Yes, but the caller's aren't checking for return
# status so what they do is unsafe by any measure
if ( $loop_check >= 10 ) {
$logger->warn('acctlock failed for 10 iterations');
return 0;
}
# first see if we can assert it right now
if (! -e $accounting_lock_file) {
if (assert_lock()) {
# and make sure we got it
my ($pid, $pname) = lock_details();
return 1 if ($pid == $$ && $pname eq $0);
}
}
# Check for an existing lock file
if ( -e $accounting_lock_file ) {
my $lock_time = 0;
my $mtime = 0;
my $start_mtime = ( stat(_) )[9];
# Wait for lock to expire
LOCKWAIT:
while ($start_mtime && -e $accounting_lock_file) {
$mtime = ( stat(_) )[9];
if ($mtime) {
# lock file has aged
if ( $mtime == $start_mtime ) {
my $will_expire_seconds = ( $lock_expire_seconds - $lock_time );
$lock_time++;
_showstatus("Waiting $will_expire_seconds seconds for accounting lock ...");
}
# there is a new lock file, reset
else {
$start_mtime = $mtime;
$lock_time = 0;
}
}
# Lock file removed by holding process
else {
last LOCKWAIT;
}
# lock file aged 100 sec
if ( $lock_time == $lock_expire_seconds ) {
if (-e $accounting_lock_file) {
my ($pid, $pname) = lock_details();
if ( $pid && $pname ) {
_showstatus("Stealing dead lock from process: $pid ($pname)");
}
else {
$logger->warn("Invalid accounting lock file $accounting_lock_file encountered");
if ( -s $accounting_lock_file ) {
$logger->warn("Lock file is not empty. Will not proceed.");
return;
}
}
# Only attempt to remove a lock file that can be read
unlink $accounting_lock_file or $logger->warn("Failed to unlink expired lock file $accounting_lock_file: $!");
last LOCKWAIT;
}
else {
$logger->warn("Lock file $accounting_lock_file has expired, but unable to read status: $!");
}
}
# now sleep if need be
sleep 1;
} # while
}
if (assert_lock()) {
# and make sure we got it
my ($pid, $pname) = lock_details();
return 1 if ($pid == $$ && $pname eq $0);
}
else {
$logger->info("Failed to grab exclusive lock: $!");
# okay this is where the recursion for $loop_check comes in
acctlock();
}
} # end of acctlock
sub assert_lock {
if ( sysopen( my $lock_fh, $accounting_lock_file, &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_EXCL, 0600 ) ) {
print {$lock_fh} $$ . "\n";
print {$lock_fh} $0 . "\n";
close $lock_fh;
_clearstatus();
return 1;
}
return 0;
} # end of assert_lock
sub acctunlock {
if ( -e $accounting_lock_file ) {
if ( open my $acct_fh, '<', $accounting_lock_file ) {
my @info = <$acct_fh>;
close($acct_fh);
chomp(@info);
close $acct_fh;
# Rely upon unlink to return success/failure
# ours? just unlnk and get out of here
if ($info[0] == $$) {
my $rtn_val = unlink $accounting_lock_file;
return $rtn_val if ( $rtn_val );
$logger->warn("Failed to unlink $accounting_lock_file: $!");
}
if ( int $info[0] != $$ ) {
if ( @info == 2 ) {
$logger->warn("Account Lock stolen from process $info[1] with PID $info[0]");
}
else {
$logger->warn('Account Lock stolen from invalid locking process');
}
}
}
else {
$logger->warn("Unable to unlock accounting lock, failed to read $accounting_lock_file: $!");
return;
}
}
return 1;
} # end of acctunlock
sub _showstatus {
my ($message) = @_;
return if !$message;
if ( -t STDIN ) {
print $message . "\n";
}
else {
cPScript::WHMAPI::setstatus($message);
}
} # end of _showstatus
sub _clearstatus {
$loop_check = 0;
if ( !-t STDIN ) {
cPScript::WHMAPI::clearstatus();
}
} # end of _clearstatus
sub lockacct { goto &acctlock; }
sub unacctlock { goto &acctunlock; }
#
# maybe just for debug
# else the caller's lvalue list is undefined
#
sub lock_details {
if (-e $accounting_lock_file) {
my $fh;
open($fh, '<', $accounting_lock_file);
my @file = <$fh>;
close($fh);
chomp(@file);
return @file;
}
} # end of lock_details
#
# a variation of lock_details
# does the check here
#
sub owned_lock {
my ($pid, $pname) = lock_details();
return 1 if ($pid && $pname && $pid == $$ && $pname eq $0);
return 0;
} # end of owned_lock
1;