mirror of
https://git.proxmox.com/git/proxmox-spamassassin
synced 2025-04-28 17:53:08 +00:00
174 lines
5.1 KiB
Perl
174 lines
5.1 KiB
Perl
# <@LICENSE>
|
|
# Licensed to the Apache Software Foundation (ASF) under one or more
|
|
# contributor license agreements. See the NOTICE file distributed with
|
|
# this work for additional information regarding copyright ownership.
|
|
# The ASF licenses this file to you under the Apache License, Version 2.0
|
|
# (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.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# </@LICENSE>
|
|
|
|
package Mail::SpamAssassin::Locker::Flock;
|
|
|
|
use strict;
|
|
use warnings;
|
|
# use bytes;
|
|
use re 'taint';
|
|
|
|
use Mail::SpamAssassin;
|
|
use Mail::SpamAssassin::Locker;
|
|
use Mail::SpamAssassin::Util;
|
|
use Mail::SpamAssassin::Logger;
|
|
use File::Spec;
|
|
use IO::File;
|
|
use Fcntl qw(:DEFAULT :flock);
|
|
|
|
our @ISA = qw(Mail::SpamAssassin::Locker);
|
|
|
|
###########################################################################
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my $self = $class->SUPER::new(@_);
|
|
$self;
|
|
}
|
|
|
|
###########################################################################
|
|
# Attempt to create a file lock, using NFS-UNsafe locking techniques.
|
|
|
|
sub safe_lock {
|
|
my ($self, $path, $max_retries, $mode) = @_;
|
|
my $is_locked = 0;
|
|
my @stat;
|
|
|
|
$max_retries ||= 30;
|
|
$mode ||= "0700";
|
|
$mode = (oct $mode) & 0666;
|
|
dbg ("locker: mode is $mode");
|
|
|
|
my $lock_file = "$path.mutex";
|
|
my $umask = umask(~$mode);
|
|
my $fh = IO::File->new;
|
|
|
|
if (!$fh->open ($lock_file, O_RDWR|O_CREAT)) {
|
|
umask $umask; # just in case
|
|
die "locker: safe_lock: cannot create lockfile $lock_file: $!\n";
|
|
}
|
|
umask $umask; # we've created the file, so reset umask
|
|
|
|
dbg("locker: safe_lock: created $lock_file");
|
|
|
|
my $unalarmed = 0;
|
|
my $oldalarm = 0;
|
|
|
|
# use a SIGALRM-based timer -- more efficient than second-by-second
|
|
# sleeps
|
|
my $eval_stat;
|
|
eval {
|
|
local $SIG{ALRM} = sub { die "alarm\n" };
|
|
dbg("locker: safe_lock: trying to get lock on $path with $max_retries timeout");
|
|
|
|
# max_retries is basically seconds! so use it for the timeout
|
|
$oldalarm = alarm $max_retries;
|
|
|
|
# HELLO!?! IO::File doesn't have a flock() method?!
|
|
if (!flock($fh, LOCK_EX)) {
|
|
warn "locker: safe_lock: cannot obtain a lock on log file: $!";
|
|
} else {
|
|
alarm $oldalarm;
|
|
$unalarmed = 1; # avoid calling alarm(0) twice
|
|
|
|
dbg("locker: safe_lock: link to $lock_file: link ok");
|
|
$is_locked = 1;
|
|
|
|
# just to be nice: let people know when it was locked
|
|
$fh->print("$$\n") or die "error writing to lock file: $!";
|
|
$fh->flush or die "cannot flush lock file: $!";
|
|
|
|
# keep the FD around - we need to keep the lockfile open or the lock
|
|
# is unlocked!
|
|
$self->{lock_fhs} ||= { };
|
|
$self->{lock_fhs}->{$path} = $fh;
|
|
}
|
|
1;
|
|
} or do {
|
|
$eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
|
|
};
|
|
|
|
$unalarmed or alarm $oldalarm; # if we die'd above, need to reset here
|
|
|
|
if (defined $eval_stat) {
|
|
if ($eval_stat =~ /alarm/) {
|
|
dbg("locker: safe_lock: timed out after $max_retries seconds");
|
|
} else {
|
|
die "locker: safe_lock: $eval_stat\n";
|
|
}
|
|
}
|
|
|
|
return $is_locked;
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
sub safe_unlock {
|
|
my ($self, $path) = @_;
|
|
|
|
if (!exists $self->{lock_fhs} || !defined $self->{lock_fhs}->{$path}) {
|
|
dbg("locker: safe_unlock: no lock handle for $path - already unlocked?");
|
|
return;
|
|
}
|
|
|
|
my $fh = $self->{lock_fhs}->{$path};
|
|
delete $self->{lock_fhs}->{$path};
|
|
|
|
flock($fh, LOCK_UN) or die "cannot unlock a log file: $!";
|
|
$fh->close or die "error closing a lock file: $!";
|
|
|
|
dbg("locker: safe_unlock: unlocked $path.mutex");
|
|
|
|
# do NOT unlink! this would open a race, whereby:
|
|
#
|
|
# procA: ....unlock (unlocked lockfile)
|
|
# procB: lock (gets lock on lockfile)
|
|
# procA: unlink (deletes lockfile)
|
|
# (procB's lock is now deleted as well!)
|
|
# procC: create, lock (gets lock on new file)
|
|
#
|
|
# both procB and procC would then think they had locks, and both
|
|
# would write to the database file. this is bad.
|
|
#
|
|
# unlink ("$path.mutex");
|
|
#
|
|
# side-effect: we leave a .mutex file around. but hey!
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
sub refresh_lock {
|
|
my($self, $path) = @_;
|
|
|
|
return unless $path;
|
|
|
|
if (!exists $self->{lock_fhs} || !defined $self->{lock_fhs}->{$path}) {
|
|
warn "locker: refresh_lock: no lock handle for $path\n";
|
|
return;
|
|
}
|
|
|
|
my $fh = $self->{lock_fhs}->{$path};
|
|
$fh->print("$$\n") or die "error writing to lock file: $!";
|
|
$fh->flush or die "cannot flush lock file: $!";
|
|
|
|
dbg("locker: refresh_lock: refresh $path.mutex");
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
1;
|