1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-06-23 00:07:44 +02:00
Files
pgbackrest/pg_backrest.pl

514 lines
19 KiB
Perl
Raw Normal View History

2013-12-11 20:39:07 -05:00
#!/usr/bin/perl
2013-11-17 13:58:21 -05:00
2014-02-19 16:06:21 -05:00
use threads;
2013-12-02 15:10:18 -05:00
use strict;
2013-12-11 20:39:07 -05:00
use warnings;
2014-02-02 19:03:05 -05:00
use File::Basename;
use Getopt::Long;
2013-12-04 21:37:45 -05:00
use Config::IniFiles;
2014-01-22 19:03:20 -05:00
use Carp;
2013-11-17 13:58:21 -05:00
2014-02-02 19:03:05 -05:00
use lib dirname($0);
use pg_backrest_utility;
use pg_backrest_file;
2014-02-03 19:57:21 -05:00
use pg_backrest_backup;
2014-02-06 15:54:22 -05:00
use pg_backrest_db;
2014-02-02 19:03:05 -05:00
# Operation constants
use constant
{
OP_ARCHIVE_PUSH => "archive-push",
OP_ARCHIVE_PULL => "archive-pull",
OP_BACKUP => "backup",
OP_EXPIRE => "expire",
};
2014-02-12 17:34:34 -05:00
use constant
{
2014-02-12 20:28:27 -05:00
CONFIG_SECTION_COMMAND => "command",
2014-02-12 17:34:34 -05:00
CONFIG_SECTION_COMMAND_OPTION => "command:option",
2014-02-13 17:11:53 -05:00
CONFIG_SECTION_LOG => "log",
2014-02-12 20:28:27 -05:00
CONFIG_SECTION_BACKUP => "backup",
CONFIG_SECTION_ARCHIVE => "archive",
CONFIG_SECTION_RETENTION => "retention",
CONFIG_SECTION_STANZA => "stanza",
CONFIG_KEY_USER => "user",
CONFIG_KEY_HOST => "host",
CONFIG_KEY_PATH => "path",
2014-02-13 12:02:45 -05:00
CONFIG_KEY_THREAD_MAX => "thread-max",
2014-02-24 09:33:47 -05:00
CONFIG_KEY_THREAD_TIMEOUT => "thread-timeout",
2014-02-13 14:54:28 -05:00
CONFIG_KEY_HARDLINK => "hardlink",
CONFIG_KEY_ARCHIVE_REQUIRED => "archive-required",
CONFIG_KEY_ARCHIVE_MAX_MB => "archive-max-mb",
2014-02-13 12:02:45 -05:00
2014-02-13 17:11:53 -05:00
CONFIG_KEY_LEVEL_FILE => "level-file",
CONFIG_KEY_LEVEL_CONSOLE => "level-console",
2014-02-12 20:28:27 -05:00
CONFIG_KEY_COMPRESS => "compress",
2014-02-13 12:02:45 -05:00
CONFIG_KEY_COMPRESS_ASYNC => "compress-async",
2014-02-12 20:28:27 -05:00
CONFIG_KEY_DECOMPRESS => "decompress",
CONFIG_KEY_CHECKSUM => "checksum",
CONFIG_KEY_MANIFEST => "manifest",
CONFIG_KEY_PSQL => "psql"
2014-02-12 17:34:34 -05:00
};
2014-02-03 20:23:04 -05:00
# Command line parameters
2014-02-12 08:04:42 -05:00
my $strConfigFile; # Configuration file
my $strStanza; # Stanza in the configuration file to load
my $strType; # Type of backup: full, differential (diff), incremental (incr)
2014-02-12 07:45:29 -05:00
GetOptions ("config=s" => \$strConfigFile,
"stanza=s" => \$strStanza,
2014-01-03 19:28:49 -05:00
"type=s" => \$strType)
or die("Error in command line arguments\n");
# Global variables
my %oConfig;
2013-12-11 20:39:07 -05:00
####################################################################################################################################
# CONFIG_LOAD - Get a value from the config and be sure that it is defined (unless bRequired is false)
####################################################################################################################################
sub config_load
{
my $strSection = shift;
my $strKey = shift;
my $bRequired = shift;
2014-02-12 07:39:42 -05:00
my $strDefault = shift;
2014-02-06 16:37:37 -05:00
# Default is that the key is not required
2013-12-11 20:39:07 -05:00
if (!defined($bRequired))
{
$bRequired = false;
}
2014-02-06 16:37:37 -05:00
my $strValue;
2014-02-06 16:37:37 -05:00
# Look in the default stanza section
2014-02-12 20:28:27 -05:00
if ($strSection eq CONFIG_SECTION_STANZA)
{
$strValue = $oConfig{"${strStanza}"}{"${strKey}"};
}
# Else look in the supplied section
else
{
# First check the stanza section
$strValue = $oConfig{"${strStanza}:${strSection}"}{"${strKey}"};
# If the stanza section value is undefined then check global
if (!defined($strValue))
{
$strValue = $oConfig{"global:${strSection}"}{"${strKey}"};
}
2013-12-11 20:39:07 -05:00
}
2014-02-06 16:37:37 -05:00
if (!defined($strValue) && $bRequired)
{
2014-02-12 07:39:42 -05:00
if (defined($strDefault))
{
return $strDefault;
}
confess &log(ERROR, "config value " . (defined($strSection) ? $strSection : "[stanza]") . "->${strKey} is undefined");
}
2014-02-06 16:37:37 -05:00
2014-02-12 17:34:34 -05:00
if ($strSection eq CONFIG_SECTION_COMMAND)
2013-12-11 20:39:07 -05:00
{
2014-02-12 17:34:34 -05:00
my $strOption = config_load(CONFIG_SECTION_COMMAND_OPTION, $strKey);
if (defined($strOption))
{
$strValue =~ s/\%option\%/${strOption}/g;
}
2013-12-11 20:39:07 -05:00
}
2014-02-06 16:37:37 -05:00
2013-12-11 20:39:07 -05:00
return $strValue;
}
2014-02-14 21:39:18 -05:00
####################################################################################################################################
2014-02-15 10:45:01 -05:00
# SAFE_EXIT - terminate all SSH sessions when the script is terminated
2014-02-14 21:39:18 -05:00
####################################################################################################################################
sub safe_exit
{
my $iTotal = backup_thread_kill();
2014-02-15 10:45:01 -05:00
confess &log(ERROR, "process was terminated on signal, ${iTotal} threads stopped");
2014-02-14 21:39:18 -05:00
}
$SIG{TERM} = \&safe_exit;
$SIG{HUP} = \&safe_exit;
$SIG{INT} = \&safe_exit;
####################################################################################################################################
# START MAIN
####################################################################################################################################
2014-02-12 08:04:42 -05:00
# Get the operation
my $strOperation = $ARGV[0];
# Validate the operation
if (!defined($strOperation))
{
2014-02-12 08:04:42 -05:00
confess &log(ERROR, "operation is not defined");
}
if ($strOperation ne OP_ARCHIVE_PUSH &&
2014-02-13 12:02:45 -05:00
$strOperation ne OP_ARCHIVE_PULL &&
$strOperation ne OP_BACKUP &&
$strOperation ne OP_EXPIRE)
2014-02-12 08:04:42 -05:00
{
confess &log(ERROR, "invalid operation ${strOperation}");
}
# Type should only be specified for backups
if (defined($strType) && $strOperation ne OP_BACKUP)
2014-02-12 08:04:42 -05:00
{
confess &log(ERROR, "type can only be specified for the backup operation")
}
2013-12-04 22:30:26 -05:00
####################################################################################################################################
# LOAD CONFIG FILE
####################################################################################################################################
2013-12-04 21:37:45 -05:00
if (!defined($strConfigFile))
{
$strConfigFile = "/etc/pg_backrest.conf";
}
2014-02-03 14:50:23 -05:00
tie %oConfig, 'Config::IniFiles', (-file => $strConfigFile) or confess &log(ERROR, "unable to find config file ${strConfigFile}");
2014-01-03 21:19:41 -05:00
# Load and check the cluster
if (!defined($strStanza))
2014-01-03 21:19:41 -05:00
{
confess "a backup stanza must be specified - show usage";
2014-01-03 21:19:41 -05:00
}
2014-02-13 17:11:53 -05:00
# Set the log levels
log_level_set(uc(config_load(CONFIG_SECTION_LOG, CONFIG_KEY_LEVEL_FILE, true, "INFO")),
uc(config_load(CONFIG_SECTION_LOG, CONFIG_KEY_LEVEL_CONSOLE, true, "ERROR")));
####################################################################################################################################
2014-02-12 20:28:27 -05:00
# ARCHIVE-PUSH Command
####################################################################################################################################
2014-02-13 12:02:45 -05:00
if ($strOperation eq OP_ARCHIVE_PUSH || $strOperation eq OP_ARCHIVE_PULL)
2013-11-17 21:48:53 -05:00
{
# If an archive section has been defined, use that instead of the backup section when operation is OP_ARCHIVE_PUSH
2014-02-12 20:28:27 -05:00
my $strSection = defined(config_load(CONFIG_SECTION_ARCHIVE, CONFIG_KEY_PATH)) ? CONFIG_SECTION_ARCHIVE : CONFIG_SECTION_BACKUP;
2014-02-13 12:02:45 -05:00
# Get the async compress flag. If compress_async=y then compression is off for the initial push
my $bCompressAsync = config_load($strSection, CONFIG_KEY_COMPRESS_ASYNC, true, "n") eq "n" ? false : true;
# Get the async compress flag. If compress_async=y then compression is off for the initial push
my $strStopFile;
my $strArchivePath;
# If logging locally then create the stop archiving file name
if ($strSection eq CONFIG_SECTION_ARCHIVE)
{
$strArchivePath = config_load(CONFIG_SECTION_ARCHIVE, CONFIG_KEY_PATH);
$strStopFile = "${strArchivePath}/lock/${strStanza}-archive.stop";
}
2014-02-12 20:28:27 -05:00
# Perform the archive-push
2014-02-13 12:02:45 -05:00
if ($strOperation eq OP_ARCHIVE_PUSH)
2014-02-12 20:28:27 -05:00
{
# Call the archive_push function
if (!defined($ARGV[1]))
{
confess &log(ERROR, "source archive file not provided - show usage");
}
# If the stop file exists then discard the archive log
if (defined($strStopFile))
{
if (-e $strStopFile)
{
&log(ERROR, "archive stop file exists ($strStopFile), discarding " . basename($ARGV[1]));
exit 0;
}
}
2014-02-13 12:02:45 -05:00
# Make sure that archive-push is running locally
if (defined(config_load(CONFIG_SECTION_STANZA, CONFIG_KEY_HOST)))
{
confess &log(ERROR, "stanza host cannot be set on archive-push - must be run locally on db server");
}
# Get the compress flag
my $bCompress = $bCompressAsync ? false : config_load($strSection, CONFIG_KEY_COMPRESS, true, "y") eq "y" ? true : false;
# Get the checksum flag
my $bChecksum = config_load($strSection, CONFIG_KEY_CHECKSUM, true, "y") eq "y" ? true : false;
# Run file_init_archive - this is the minimal config needed to run archiving
my $oFile = pg_backrest_file->new
(
strStanza => $strStanza,
bNoCompression => !$bCompress,
strBackupUser => config_load($strSection, CONFIG_KEY_USER),
strBackupHost => config_load($strSection, CONFIG_KEY_HOST),
strBackupPath => config_load($strSection, CONFIG_KEY_PATH, true),
strCommandChecksum => config_load(CONFIG_SECTION_COMMAND, CONFIG_KEY_CHECKSUM, $bChecksum),
strCommandCompress => config_load(CONFIG_SECTION_COMMAND, CONFIG_KEY_COMPRESS, $bCompress),
strCommandDecompress => config_load(CONFIG_SECTION_COMMAND, CONFIG_KEY_DECOMPRESS, $bCompress)
);
backup_init
(
undef,
$oFile,
undef,
undef,
!$bChecksum
);
2014-02-13 17:47:53 -05:00
&log(INFO, "pushing archive log " . $ARGV[1] . ($bCompressAsync ? " asynchronously" : ""));
2014-02-13 12:02:45 -05:00
archive_push($ARGV[1]);
# Only continue if we are archiving local and a backup server is defined
if (!($strSection eq CONFIG_SECTION_ARCHIVE && defined(config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_HOST))))
{
exit 0;
}
# Set the operation so that archive-pull will be called next
$strOperation = OP_ARCHIVE_PULL;
# fork and exit the parent process
if (fork())
{
exit 0;
}
}
2013-12-04 21:37:45 -05:00
# Perform the archive-pull
2014-02-13 12:02:45 -05:00
if ($strOperation eq OP_ARCHIVE_PULL)
{
# Make sure that archive-pull is running on the db server
if (defined(config_load(CONFIG_SECTION_STANZA, CONFIG_KEY_HOST)))
{
confess &log(ERROR, "stanza host cannot be set on archive-pull - must be run locally on db server");
}
# Create a lock file to make sure archive-pull does not run more than once
my $strLockFile = "${strArchivePath}/lock/${strStanza}-archive.lock";
2014-02-12 20:28:27 -05:00
if (!lock_file_create($strLockFile))
2014-02-13 12:02:45 -05:00
{
2014-02-13 20:33:05 -05:00
&log(DEBUG, "archive-pull process is already running - exiting");
2014-02-13 12:02:45 -05:00
exit 0
}
2014-02-16 16:51:05 -05:00
# Build the basic command string that will be used to modify the command during processing
2014-02-16 16:58:25 -05:00
my $strCommand = $^X . " " . $0 . " --stanza=${strStanza}";
2014-02-16 16:51:05 -05:00
2014-02-13 12:02:45 -05:00
# Get the new operational flags
my $bCompress = config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_COMPRESS, true, "y") eq "y" ? true : false;
my $bChecksum = config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_CHECKSUM, true, "y") eq "y" ? true : false;
my $iArchiveMaxMB = config_load(CONFIG_SECTION_ARCHIVE, CONFIG_KEY_ARCHIVE_MAX_MB);
2014-02-13 12:02:45 -05:00
eval
{
# Run file_init_archive - this is the minimal config needed to run archive pulling
my $oFile = pg_backrest_file->new
(
strStanza => $strStanza,
bNoCompression => !$bCompress,
strBackupUser => config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_USER),
strBackupHost => config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_HOST),
strBackupPath => config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_PATH, true),
strCommandChecksum => config_load(CONFIG_SECTION_COMMAND, CONFIG_KEY_CHECKSUM, $bChecksum),
strCommandCompress => config_load(CONFIG_SECTION_COMMAND, CONFIG_KEY_COMPRESS, $bCompress),
strCommandDecompress => config_load(CONFIG_SECTION_COMMAND, CONFIG_KEY_DECOMPRESS, $bCompress),
strCommandManifest => config_load(CONFIG_SECTION_COMMAND, CONFIG_KEY_MANIFEST)
);
backup_init
(
undef,
$oFile,
undef,
undef,
!$bChecksum,
2014-02-24 09:33:47 -05:00
config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_THREAD_MAX),
undef,
config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_THREAD_TIMEOUT)
);
# Call the archive_pull function Continue to loop as long as there are files to process.
while (archive_pull($strArchivePath . "/archive/${strStanza}", $strStopFile, $strCommand, $iArchiveMaxMB))
{
}
};
# If there were errors above then start compressing
if ($@)
2014-02-13 12:02:45 -05:00
{
if ($bCompressAsync)
{
&log(ERROR, "error during transfer: $@");
2014-02-24 09:33:47 -05:00
&log(WARN, "errors during transfer, starting compression");
# Run file_init_archive - this is the minimal config needed to run archive pulling !!! need to close the old file
my $oFile = pg_backrest_file->new
(
strStanza => $strStanza,
bNoCompression => false,
strBackupPath => config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_PATH, true),
strCommandChecksum => config_load(CONFIG_SECTION_COMMAND, CONFIG_KEY_CHECKSUM, $bChecksum),
strCommandCompress => config_load(CONFIG_SECTION_COMMAND, CONFIG_KEY_COMPRESS, $bCompress),
strCommandDecompress => config_load(CONFIG_SECTION_COMMAND, CONFIG_KEY_DECOMPRESS, $bCompress),
strCommandManifest => config_load(CONFIG_SECTION_COMMAND, CONFIG_KEY_MANIFEST)
);
backup_init
(
undef,
$oFile,
undef,
undef,
!$bChecksum,
2014-02-24 09:33:47 -05:00
config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_THREAD_MAX),
undef,
config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_THREAD_TIMEOUT)
);
archive_compress($strArchivePath . "/archive/${strStanza}", $strCommand, 256);
}
else
{
confess $@;
}
2014-02-13 12:02:45 -05:00
}
lock_file_remove();
2014-02-13 12:02:45 -05:00
}
2013-12-04 22:30:26 -05:00
exit 0;
}
2014-02-13 13:54:43 -05:00
####################################################################################################################################
# OPEN THE LOG FILE
####################################################################################################################################
if (defined(config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_HOST)))
{
confess &log(ASSERT, "backup/expire operations must be performed locally on the backup server");
}
log_file_set(config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_PATH, true) . "/log/${strStanza}");
2013-12-04 22:30:26 -05:00
####################################################################################################################################
# GET MORE CONFIG INFO
####################################################################################################################################
2014-02-12 08:04:42 -05:00
# Set the backup type
if (!defined($strType))
{
$strType = "incremental";
}
elsif ($strType eq "diff")
2014-01-03 19:28:49 -05:00
{
$strType = "differential";
}
2014-02-12 08:04:42 -05:00
elsif ($strType eq "incr")
2014-01-03 19:28:49 -05:00
{
$strType = "incremental";
}
2014-02-12 08:04:42 -05:00
elsif ($strType ne "full" && $strType ne "differential" && $strType ne "incremental")
2014-01-03 19:28:49 -05:00
{
2014-02-03 19:03:17 -05:00
confess &log(ERROR, "backup type must be full, differential (diff), incremental (incr)");
2014-01-03 19:28:49 -05:00
}
2014-02-12 07:39:42 -05:00
# Get the operational flags
2014-02-12 20:28:27 -05:00
my $bCompress = config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_COMPRESS, true, "y") eq "y" ? true : false;
my $bChecksum = config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_CHECKSUM, true, "y") eq "y" ? true : false;
2014-02-12 07:39:42 -05:00
2014-02-03 20:48:02 -05:00
# Run file_init_archive - the rest of the file config required for backup and restore
2014-02-05 21:39:08 -05:00
my $oFile = pg_backrest_file->new
2014-02-03 14:50:23 -05:00
(
strStanza => $strStanza,
2014-02-12 07:39:42 -05:00
bNoCompression => !$bCompress,
2014-02-12 20:28:27 -05:00
strBackupUser => config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_USER),
strBackupHost => config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_HOST),
strBackupPath => config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_PATH, true),
strDbUser => config_load(CONFIG_SECTION_STANZA, CONFIG_KEY_USER),
strDbHost => config_load(CONFIG_SECTION_STANZA, CONFIG_KEY_HOST),
strCommandChecksum => config_load(CONFIG_SECTION_COMMAND, CONFIG_KEY_CHECKSUM, $bChecksum),
strCommandCompress => config_load(CONFIG_SECTION_COMMAND, CONFIG_KEY_COMPRESS, $bCompress),
strCommandDecompress => config_load(CONFIG_SECTION_COMMAND, CONFIG_KEY_DECOMPRESS, $bCompress),
strCommandManifest => config_load(CONFIG_SECTION_COMMAND, CONFIG_KEY_MANIFEST),
strCommandPsql => config_load(CONFIG_SECTION_COMMAND, CONFIG_KEY_PSQL)
2014-02-03 14:50:23 -05:00
);
2014-02-01 11:04:33 -05:00
2014-02-06 15:54:22 -05:00
my $oDb = pg_backrest_db->new
(
2014-02-12 20:28:27 -05:00
strDbUser => config_load(CONFIG_SECTION_STANZA, CONFIG_KEY_USER),
strDbHost => config_load(CONFIG_SECTION_STANZA, CONFIG_KEY_HOST),
strCommandPsql => config_load(CONFIG_SECTION_COMMAND, CONFIG_KEY_PSQL),
oDbSSH => $oFile->{oDbSSH}
2014-02-06 15:54:22 -05:00
);
2014-02-03 19:57:21 -05:00
# Run backup_init - parameters required for backup and restore operations
backup_init
(
2014-02-06 15:54:22 -05:00
$oDb,
2014-02-05 21:39:08 -05:00
$oFile,
2014-02-03 19:57:21 -05:00
$strType,
2014-02-13 14:54:28 -05:00
config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_HARDLINK, true, "n") eq "y" ? true : false,
2014-02-12 07:39:42 -05:00
!$bChecksum,
2014-02-13 12:02:45 -05:00
config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_THREAD_MAX),
2014-02-24 09:33:47 -05:00
config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_ARCHIVE_REQUIRED, true, "y") eq "y" ? true : false,
config_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_THREAD_TIMEOUT)
2014-02-03 19:57:21 -05:00
);
2013-12-04 21:37:45 -05:00
####################################################################################################################################
# BACKUP
####################################################################################################################################
if ($strOperation eq OP_BACKUP)
2013-12-04 21:37:45 -05:00
{
my $strLockFile = $oFile->path_get(PATH_BACKUP, "lock/${strStanza}-backup.lock");
if (!lock_file_create($strLockFile))
{
&log(DEBUG, "backup process is already running for stanza ${strStanza} - exiting");
exit 0
}
2014-02-12 20:28:27 -05:00
backup(config_load(CONFIG_SECTION_STANZA, CONFIG_KEY_PATH));
$strOperation = OP_EXPIRE;
2014-02-14 21:39:18 -05:00
sleep(30);
lock_file_remove();
}
####################################################################################################################################
# EXPIRE
####################################################################################################################################
if ($strOperation eq OP_EXPIRE)
{
my $strLockFile = $oFile->path_get(PATH_BACKUP, "lock/${strStanza}-expire.lock");
if (!lock_file_create($strLockFile))
{
&log(DEBUG, "expire process is already running for stanza ${strStanza} - exiting");
exit 0
}
backup_expire
(
$oFile->path_get(PATH_BACKUP_CLUSTER),
2014-02-12 17:34:34 -05:00
config_load(CONFIG_SECTION_RETENTION, "full_retention"),
config_load(CONFIG_SECTION_RETENTION, "differential_retention"),
config_load(CONFIG_SECTION_RETENTION, "archive_retention_type"),
config_load(CONFIG_SECTION_RETENTION, "archive_retention")
);
lock_file_remove();
2014-01-15 20:41:29 -05:00
exit 0;
2014-02-12 08:04:42 -05:00
}
confess &log(ASSERT, "invalid operation ${strOperation} - missing handler block");