mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2024-12-14 10:13:05 +02:00
6fd0c3dcaa
Now only two types of locks can be taken: archive and backup. Most commands use one or the other but the stanza-* commands acquire both locks. This provides better protection than the old command-based locking scheme.
223 lines
7.3 KiB
Perl
223 lines
7.3 KiB
Perl
####################################################################################################################################
|
|
# COMMON LOCK MODULE
|
|
####################################################################################################################################
|
|
package pgBackRest::Common::Lock;
|
|
|
|
use strict;
|
|
use warnings FATAL => qw(all);
|
|
use Carp qw(confess);
|
|
|
|
use Exporter qw(import);
|
|
our @EXPORT = qw();
|
|
use Fcntl qw(:DEFAULT :flock);
|
|
use File::Basename qw(dirname);
|
|
|
|
use pgBackRest::Common::Exception;
|
|
use pgBackRest::Common::Log;
|
|
use pgBackRest::Common::String;
|
|
use pgBackRest::Common::Wait;
|
|
use pgBackRest::Config::Config;
|
|
use pgBackRest::Storage::Helper;
|
|
|
|
####################################################################################################################################
|
|
# lockPathCreate
|
|
#
|
|
# Create the lock path if it does not exist.
|
|
####################################################################################################################################
|
|
sub lockPathCreate
|
|
{
|
|
storageLocal()->pathCreate(cfgOption(CFGOPT_LOCK_PATH), {strMode => '770', bIgnoreExists => true, bCreateParent => true});
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# lockStopFileName
|
|
#
|
|
# Get the stop file name.
|
|
####################################################################################################################################
|
|
sub lockStopFileName
|
|
{
|
|
my $strStanza = shift;
|
|
|
|
return cfgOption(CFGOPT_LOCK_PATH) . (defined($strStanza) ? "/${strStanza}" : '/all') . '.stop';
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# lockStop
|
|
#
|
|
# Write a stop file that will prevent backrest processes from running. Optionally attempt to kill any processes that are currently
|
|
# running.
|
|
####################################################################################################################################
|
|
sub lockStop
|
|
{
|
|
# Create the lock path
|
|
lockPathCreate();
|
|
|
|
# Generate the stop file name
|
|
my $strStopFile = lockStopFileName(cfgOption(CFGOPT_STANZA, false));
|
|
|
|
# If the stop file already exists then warn
|
|
if (-e $strStopFile)
|
|
{
|
|
&log(WARN, 'stop file already exists' .
|
|
(cfgOptionTest(CFGOPT_STANZA) ? ' for stanza ' . cfgOption(CFGOPT_STANZA) : ' for all stanzas'));
|
|
return false;
|
|
}
|
|
|
|
# Create the stop file
|
|
sysopen(my $hStopHandle, $strStopFile, O_WRONLY | O_CREAT, oct(640))
|
|
or confess &log(ERROR, "unable to open stop file ${strStopFile}", ERROR_FILE_OPEN);
|
|
close($hStopHandle);
|
|
|
|
# If --force was specified then send term signals to running processes
|
|
if (cfgOption(CFGOPT_FORCE))
|
|
{
|
|
my $strLockPath = cfgOption(CFGOPT_LOCK_PATH);
|
|
|
|
opendir(my $hPath, $strLockPath)
|
|
or confess &log(ERROR, "unable to open lock path ${strLockPath}", ERROR_PATH_OPEN);
|
|
|
|
my @stryFileList = grep(!/^(\.)|(\.\.)$/i, readdir($hPath));
|
|
|
|
# Find each lock file and send term signals to the processes
|
|
foreach my $strFile (sort(@stryFileList))
|
|
{
|
|
my $hLockHandle;
|
|
my $strLockFile = "${strLockPath}/${strFile}";
|
|
|
|
# Skip if this is a stop file
|
|
next if ($strFile =~ /\.stop$/);
|
|
|
|
# Open the lock file for read
|
|
if (!sysopen($hLockHandle, $strLockFile, O_RDONLY))
|
|
{
|
|
&log(WARN, "unable to open lock file ${strLockFile}");
|
|
next;
|
|
}
|
|
|
|
# Attempt a lock on the file - if a lock can be acquired that means the original process died without removing the
|
|
# lock file so we'll remove it now.
|
|
if (flock($hLockHandle, LOCK_EX | LOCK_NB))
|
|
{
|
|
unlink($strLockFile);
|
|
close($hLockHandle);
|
|
next;
|
|
}
|
|
|
|
# The file is locked so that means there is a running process - read the process id and send it a term signal
|
|
my $iProcessId = trim(readline($hLockHandle));
|
|
|
|
# If the process id is defined then this is a valid lock file
|
|
if (defined($iProcessId))
|
|
{
|
|
if (!kill('TERM', $iProcessId))
|
|
{
|
|
&log(WARN, "unable to send term signal to process ${iProcessId}");
|
|
}
|
|
|
|
&log(INFO, "sent term signal to process ${iProcessId}");
|
|
}
|
|
# Else not a valid lock file so delete it
|
|
{
|
|
unlink($strLockFile);
|
|
close($hLockHandle);
|
|
next;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
push @EXPORT, qw(lockStop);
|
|
|
|
####################################################################################################################################
|
|
# lockStopTest
|
|
#
|
|
# Test if a stop file exists for the current stanza or all stanzas.
|
|
####################################################################################################################################
|
|
sub lockStopTest
|
|
{
|
|
# Assign function parameters, defaults, and log debug info
|
|
my
|
|
(
|
|
$strOperation,
|
|
$bStanzaStopRequired,
|
|
) =
|
|
logDebugParam
|
|
(
|
|
__PACKAGE__ . '::lockStopTest', \@_,
|
|
{name => 'bStanzaStopRequired', optional => true, default => false}
|
|
);
|
|
|
|
my $bStopExists = false;
|
|
|
|
# Check the stanza first if it is specified
|
|
if (cfgOptionTest(CFGOPT_STANZA))
|
|
{
|
|
# Generate the stop file name
|
|
my $strStopFile = lockStopFileName(cfgOption(CFGOPT_STANZA));
|
|
|
|
if (-e $strStopFile)
|
|
{
|
|
# If the stop file exists and is required then set the flag to true
|
|
if ($bStanzaStopRequired)
|
|
{
|
|
$bStopExists = true;
|
|
}
|
|
else
|
|
{
|
|
confess &log(ERROR, 'stop file exists for stanza ' . cfgOption(CFGOPT_STANZA), ERROR_STOP);
|
|
}
|
|
}
|
|
}
|
|
|
|
# If not looking for a specific stanza stop file, then check all stanzas
|
|
if (!$bStanzaStopRequired)
|
|
{
|
|
# Now check all stanzas
|
|
my $strStopFile = lockStopFileName();
|
|
|
|
if (-e $strStopFile)
|
|
{
|
|
confess &log(ERROR, 'stop file exists for all stanzas', ERROR_STOP);
|
|
}
|
|
}
|
|
|
|
# Return from function and log return values if any
|
|
return logDebugReturn
|
|
(
|
|
$strOperation,
|
|
{name => 'bStopExists', value => $bStopExists}
|
|
);
|
|
}
|
|
|
|
push @EXPORT, qw(lockStopTest);
|
|
|
|
####################################################################################################################################
|
|
# lockStart
|
|
#
|
|
# Remove the stop file so processes can run.
|
|
####################################################################################################################################
|
|
sub lockStart
|
|
{
|
|
# Generate the stop file name
|
|
my $strStopFile = lockStopFileName(cfgOption(CFGOPT_STANZA, false));
|
|
|
|
# If the stop file doesn't exist then warn
|
|
if (!-e $strStopFile)
|
|
{
|
|
&log(WARN, 'stop file does not exist' . (cfgOptionTest(CFGOPT_STANZA) ? ' for stanza ' . cfgOption(CFGOPT_STANZA) : ''));
|
|
return false;
|
|
}
|
|
|
|
# Remove the stop file
|
|
unlink($strStopFile)
|
|
or confess &log(ERROR, "unable to remove ${strStopFile}: $!");
|
|
|
|
return true;
|
|
}
|
|
|
|
push @EXPORT, qw(lockStart);
|
|
|
|
1;
|