proxmox-spamassassin/upstream/lib/Mail/SpamAssassin/Locker/Flock.pm
Stoiko Ivanov ae52237fd8 update SpamAssassin to 4.0.0
generated by make update-upstream

Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
2023-03-13 21:13:17 +01:00

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;