2014-02-03 03:03:05 +03:00
####################################################################################################################################
# FILE MODULE
####################################################################################################################################
package pg_backrest_file ;
2014-02-06 05:39:08 +03:00
use Moose ;
2014-02-03 03:03:05 +03:00
use strict ;
use warnings ;
use Carp ;
use Net::OpenSSH ;
use IPC::Open3 ;
use File::Basename ;
use IPC::System::Simple qw( capture ) ;
use lib dirname ( $ 0 ) ;
use pg_backrest_utility ;
use Exporter qw( import ) ;
2014-02-06 05:39:08 +03:00
our @ EXPORT = qw( PATH_DB PATH_DB_ABSOLUTE PATH_BACKUP PATH_BACKUP_ABSOLUTE PATH_BACKUP_CLUSTER PATH_BACKUP_TMP PATH_BACKUP_ARCHIVE ) ;
2014-02-03 03:03:05 +03:00
# Extension and permissions
2014-02-06 05:39:08 +03:00
has strCompressExtension = > ( is = > 'ro' , default = > 'gz' ) ;
has strDefaultPathPermission = > ( is = > 'bare' , default = > '0750' ) ;
has strDefaultFilePermission = > ( is = > 'ro' , default = > '0640' ) ;
2014-02-03 03:03:05 +03:00
# Command strings
2014-02-06 05:39:08 +03:00
has strCommandChecksum = > ( is = > 'bare' ) ;
has strCommandCompress = > ( is = > 'bare' ) ;
has strCommandDecompress = > ( is = > 'bare' ) ;
has strCommandCat = > ( is = > 'bare' , default = > 'cat %file%' ) ;
has strCommandManifest = > ( is = > 'bare' ) ;
2014-02-04 04:48:02 +03:00
# Module variables
2014-02-06 05:39:08 +03:00
has strDbUser = > ( is = > 'bare' ) ; # Database user
has strDbHost = > ( is = > 'bare' ) ; # Database host
has oDbSSH = > ( is = > 'bare' ) ; # Database SSH object
2014-02-04 04:48:02 +03:00
2014-02-06 05:39:08 +03:00
has strBackupUser = > ( is = > 'bare' ) ; # Backup user
has strBackupHost = > ( is = > 'bare' ) ; # Backup host
has oBackupSSH = > ( is = > 'bare' ) ; # Backup SSH object
has strBackupPath = > ( is = > 'bare' ) ; # Backup base path
has strBackupClusterPath = > ( is = > 'bare' ) ; # Backup cluster path
2014-02-03 03:03:05 +03:00
# Process flags
2014-02-06 05:39:08 +03:00
has bNoCompression = > ( is = > 'bare' ) ;
2014-02-06 20:49:54 +03:00
has strStanza = > ( is = > 'bare' ) ;
2014-02-11 23:31:16 +03:00
has iThreadIdx = > ( is = > 'bare' ) ;
2014-02-03 03:03:05 +03:00
2014-02-07 00:37:37 +03:00
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
2014-02-06 06:26:10 +03:00
sub BUILD
2014-02-03 03:03:05 +03:00
{
2014-02-06 05:39:08 +03:00
my $ self = shift ;
2014-02-03 22:50:23 +03:00
# Make sure the backup path is defined
2014-02-06 05:39:08 +03:00
if ( ! defined ( $ self - > { strBackupPath } ) )
2014-02-03 22:50:23 +03:00
{
2014-02-04 03:03:17 +03:00
confess & log ( ERROR , "common:backup_path undefined" ) ;
2014-02-03 22:50:23 +03:00
}
2014-02-06 05:39:08 +03:00
2014-02-03 22:50:23 +03:00
# Create the backup cluster path
2014-02-06 20:49:54 +03:00
$ self - > { strBackupClusterPath } = $ self - > { strBackupPath } . "/" . $ self - > { strStanza } ;
2014-02-03 22:50:23 +03:00
2014-02-17 03:01:06 +03:00
# Create the ssh options string
if ( defined ( $ self - > { strBackupHost } ) || defined ( $ self - > { strDbHost } ) )
2014-02-03 22:50:23 +03:00
{
2014-02-17 03:01:06 +03:00
my $ strOptionSSH = "Compression=no" ;
if ( $ self - > { bNoCompression } )
{
$ strOptionSSH = "Compression=yes" ;
}
# Connect SSH object if backup host is defined
if ( defined ( $ self - > { strBackupHost } ) )
{
& log ( TRACE , "connecting to backup ssh host " . $ self - > { strBackupHost } ) ;
# !!! This could be improved by redirecting stderr to a file to get a better error message
$ self - > { oBackupSSH } = Net::OpenSSH - > new ( $ self - > { strBackupHost } , master_stderr_discard = > true , user = > $ self - > { strBackupUser } , master_opts = > [ - o = > $ strOptionSSH ] ) ;
$ self - > { oBackupSSH } - > error and confess & log ( ERROR , "unable to connect to $self->{strBackupHost}: " . $ self - > { oBackupSSH } - > error ) ;
}
# Connect SSH object if db host is defined
if ( defined ( $ self - > { strDbHost } ) )
{
& log ( TRACE , "connecting to database ssh host $self->{strDbHost}" ) ;
# !!! This could be improved by redirecting stderr to a file to get a better error message
$ self - > { oDbSSH } = Net::OpenSSH - > new ( $ self - > { strDbHost } , master_stderr_discard = > true , user = > $ self - > { strDbUser } , master_opts = > [ - o = > $ strOptionSSH ] ) ;
$ self - > { oDbSSH } - > error and confess & log ( ERROR , "unable to connect to $self->{strDbHost}: " . $ self - > { oDbSSH } - > error ) ;
}
2014-02-03 22:50:23 +03:00
}
2014-02-03 03:03:05 +03:00
}
2014-02-11 23:31:16 +03:00
####################################################################################################################################
# CLONE
####################################################################################################################################
sub clone
{
my $ self = shift ;
my $ iThreadIdx = shift ;
return pg_backrest_file - > new
(
strCompressExtension = > $ self - > { strCompressExtension } ,
strDefaultPathPermission = > $ self - > { strDefaultPathPermission } ,
strDefaultFilePermission = > $ self - > { strDefaultFilePermission } ,
strCommandChecksum = > $ self - > { strCommandChecksum } ,
strCommandCompress = > $ self - > { strCommandCompress } ,
strCommandDecompress = > $ self - > { strCommandDecompress } ,
strCommandCat = > $ self - > { strCommandCat } ,
strCommandManifest = > $ self - > { strCommandManifest } ,
strDbUser = > $ self - > { strDbUser } ,
strDbHost = > $ self - > { strDbHost } ,
strBackupUser = > $ self - > { strBackupUser } ,
strBackupHost = > $ self - > { strBackupHost } ,
strBackupPath = > $ self - > { strBackupPath } ,
strBackupClusterPath = > $ self - > { strBackupClusterPath } ,
bNoCompression = > $ self - > { bNoCompression } ,
strStanza = > $ self - > { strStanza } ,
iThreadIdx = > $ iThreadIdx
) ;
}
2014-02-03 03:03:05 +03:00
####################################################################################################################################
# PATH_GET
####################################################################################################################################
use constant
{
2014-02-04 03:03:17 +03:00
PATH_DB = > 'db' ,
PATH_DB_ABSOLUTE = > 'db:absolute' ,
PATH_BACKUP = > 'backup' ,
PATH_BACKUP_ABSOLUTE = > 'backup:absolute' ,
PATH_BACKUP_CLUSTER = > 'backup:cluster' ,
PATH_BACKUP_TMP = > 'backup:tmp' ,
PATH_BACKUP_ARCHIVE = > 'backup:archive'
2014-02-03 03:03:05 +03:00
} ;
sub path_type_get
{
2014-02-06 05:39:08 +03:00
my $ self = shift ;
2014-02-03 03:03:05 +03:00
my $ strType = shift ;
2014-02-07 00:37:37 +03:00
2014-02-03 03:03:05 +03:00
# If db type
if ( $ strType =~ /^db(\:.*){0,1}/ )
{
return PATH_DB ;
}
# Else if backup type
elsif ( $ strType =~ /^backup(\:.*){0,1}/ )
{
return PATH_BACKUP ;
}
2014-02-07 00:37:37 +03:00
2014-02-03 03:03:05 +03:00
# Error when path type not recognized
confess & log ( ASSERT , "no known path types in '${strType}'" ) ;
}
sub path_get
{
2014-02-06 05:39:08 +03:00
my $ self = shift ;
2014-02-05 23:56:05 +03:00
my $ strType = shift ; # Base type of the path to get (PATH_DB_ABSOLUTE, PATH_BACKUP_TMP, etc)
my $ strFile = shift ; # File to append to the base path (can include a path as well)
my $ bTemp = shift ; # Return the temp file for this path type - only some types have temp files
2014-02-03 03:03:05 +03:00
2014-02-05 23:56:05 +03:00
# Only allow temp files for PATH_BACKUP_ARCHIVE and PATH_BACKUP_TMP
2014-02-03 03:03:05 +03:00
if ( defined ( $ bTemp ) && $ bTemp && ! ( $ strType eq PATH_BACKUP_ARCHIVE || $ strType eq PATH_BACKUP_TMP ) )
{
confess & log ( ASSERT , "temp file not supported on path " . $ strType ) ;
}
2014-02-05 23:56:05 +03:00
# Get absolute db path
2014-02-03 03:03:05 +03:00
if ( $ strType eq PATH_DB_ABSOLUTE )
{
return $ strFile ;
}
2014-02-05 23:56:05 +03:00
# Make sure the base backup path is defined
2014-02-06 05:39:08 +03:00
if ( ! defined ( $ self - > { strBackupPath } ) )
2014-02-05 21:10:36 +03:00
{
2014-02-05 23:56:05 +03:00
confess & log ( ASSERT , "\$strBackupPath not yet defined" ) ;
2014-02-05 21:10:36 +03:00
}
2014-02-05 23:56:05 +03:00
# Get absolute backup path
if ( $ strType eq PATH_BACKUP_ABSOLUTE )
2014-02-03 03:03:05 +03:00
{
2014-02-05 23:56:05 +03:00
# Need a check in here to make sure this is relative to the backup path
2014-02-04 03:03:17 +03:00
2014-02-05 23:56:05 +03:00
return $ strFile ;
}
2014-02-05 21:10:36 +03:00
2014-02-05 23:56:05 +03:00
# Get base backup path
if ( $ strType eq PATH_BACKUP )
{
2014-02-06 05:39:08 +03:00
return $ self - > { strBackupPath } . ( defined ( $ strFile ) ? "/${strFile}" : "" ) ;
2014-02-05 23:56:05 +03:00
}
2014-02-05 21:10:36 +03:00
2014-02-05 23:56:05 +03:00
# Make sure the cluster is defined
2014-02-06 20:49:54 +03:00
if ( ! defined ( $ self - > { strStanza } ) )
2014-02-05 23:56:05 +03:00
{
2014-02-06 20:49:54 +03:00
confess & log ( ASSERT , "\$strStanza not yet defined" ) ;
2014-02-05 23:56:05 +03:00
}
2014-02-05 21:10:36 +03:00
2014-02-05 23:56:05 +03:00
# Get the backup tmp path
if ( $ strType eq PATH_BACKUP_TMP )
{
2014-02-11 23:31:16 +03:00
my $ strTempPath = "$self->{strBackupPath}/temp/$self->{strStanza}.tmp" ;
2014-02-05 21:10:36 +03:00
2014-02-05 23:56:05 +03:00
if ( defined ( $ bTemp ) && $ bTemp )
{
2014-02-11 23:31:16 +03:00
return "${strTempPath}/file.tmp" . ( defined ( $ self - > { iThreadIdx } ) ? ".$self->{iThreadIdx}" : "" ) ;
2014-02-05 21:10:36 +03:00
}
2014-02-05 23:56:05 +03:00
return "${strTempPath}" . ( defined ( $ strFile ) ? "/${strFile}" : "" ) ;
2014-02-04 03:03:17 +03:00
}
2014-02-05 23:56:05 +03:00
# Get the backup archive path
if ( $ strType eq PATH_BACKUP_ARCHIVE )
2014-02-03 03:03:05 +03:00
{
2014-02-06 20:49:54 +03:00
my $ strArchivePath = "$self->{strBackupPath}/archive/$self->{strStanza}" ;
2014-02-05 23:56:05 +03:00
my $ strArchive ;
2014-02-04 03:03:17 +03:00
2014-02-13 23:26:07 +03:00
if ( defined ( $ bTemp ) && $ bTemp )
{
return "${strArchivePath}/file.tmp" . ( defined ( $ self - > { iThreadIdx } ) ? ".$self->{iThreadIdx}" : "" ) ;
}
2014-02-05 23:56:05 +03:00
if ( defined ( $ strFile ) )
2014-02-03 03:03:05 +03:00
{
2014-02-05 23:56:05 +03:00
$ strArchive = substr ( basename ( $ strFile ) , 0 , 24 ) ;
if ( $ strArchive !~ /^([0-F]){24}$/ )
2014-02-03 03:03:05 +03:00
{
2014-02-05 23:56:05 +03:00
return "${strArchivePath}/${strFile}" ;
2014-02-03 03:03:05 +03:00
}
2014-02-05 23:56:05 +03:00
}
2014-02-04 03:03:17 +03:00
2014-02-05 23:56:05 +03:00
return $ strArchivePath . ( defined ( $ strArchive ) ? "/" . substr ( $ strArchive , 0 , 16 ) : "" ) .
( defined ( $ strFile ) ? "/" . $ strFile : "" ) ;
}
2014-02-04 03:03:17 +03:00
2014-02-05 23:56:05 +03:00
if ( $ strType eq PATH_BACKUP_CLUSTER )
{
2014-02-06 20:49:54 +03:00
return $ self - > { strBackupPath } . "/backup/$self->{strStanza}" . ( defined ( $ strFile ) ? "/${strFile}" : "" ) ;
2014-02-03 03:03:05 +03:00
}
# Error when path type not recognized
confess & log ( ASSERT , "no known path types in '${strType}'" ) ;
}
####################################################################################################################################
# LINK_CREATE
####################################################################################################################################
sub link_create
{
2014-02-06 05:39:08 +03:00
my $ self = shift ;
2014-02-03 03:03:05 +03:00
my $ strSourcePathType = shift ;
my $ strSourceFile = shift ;
my $ strDestinationPathType = shift ;
my $ strDestinationFile = shift ;
my $ bHard = shift ;
my $ bRelative = shift ;
2014-02-14 17:05:14 +03:00
my $ bPathCreate = shift ;
2014-02-07 00:37:37 +03:00
2014-02-05 18:40:49 +03:00
# if bHard is not defined default to false
$ bHard = defined ( $ bHard ) ? $ bHard : false ;
2014-02-07 00:37:37 +03:00
2014-02-05 18:40:49 +03:00
# if bRelative is not defined or bHard is true, default to false
$ bRelative = ! defined ( $ bRelative ) || $ bHard ? false : $ bRelative ;
2014-02-14 17:05:14 +03:00
# if bPathCreate is not defined, default to true
$ bPathCreate = defined ( $ bPathCreate ) ? $ bPathCreate : true ;
2014-02-05 18:40:49 +03:00
# Source and destination path types must be the same (both PATH_DB or both PATH_BACKUP)
2014-02-06 05:39:08 +03:00
if ( $ self - > path_type_get ( $ strSourcePathType ) ne $ self - > path_type_get ( $ strDestinationPathType ) )
2014-02-04 03:03:17 +03:00
{
confess & log ( ASSERT , "path types must be equal in link create" ) ;
}
2014-02-05 18:40:49 +03:00
# Generate source and destination files
2014-02-06 05:39:08 +03:00
my $ strSource = $ self - > path_get ( $ strSourcePathType , $ strSourceFile ) ;
my $ strDestination = $ self - > path_get ( $ strDestinationPathType , $ strDestinationFile ) ;
2014-02-03 03:03:05 +03:00
2014-02-04 03:03:17 +03:00
# If the destination path is backup and does not exist, create it
2014-02-14 17:05:14 +03:00
if ( $ bPathCreate && $ self - > path_type_get ( $ strDestinationPathType ) eq PATH_BACKUP )
2014-02-03 03:03:05 +03:00
{
2014-02-06 05:39:08 +03:00
$ self - > path_create ( PATH_BACKUP_ABSOLUTE , dirname ( $ strDestination ) ) ;
2014-02-03 03:03:05 +03:00
}
2014-02-07 00:37:37 +03:00
2014-02-03 03:03:05 +03:00
unless ( - e $ strSource )
{
2014-02-06 05:39:08 +03:00
if ( - e $ strSource . ".$self->{strCompressExtension}" )
2014-02-03 03:03:05 +03:00
{
2014-02-06 05:39:08 +03:00
$ strSource . = ".$self->{strCompressExtension}" ;
$ strDestination . = ".$self->{strCompressExtension}" ;
2014-02-03 03:03:05 +03:00
}
else
{
2014-02-05 18:40:49 +03:00
# Error when a hardlink will be created on a missing file
if ( $ bHard )
{
2014-02-06 05:39:08 +03:00
confess & log ( ASSERT , "unable to find ${strSource}(.$self->{strCompressExtension}) for link" ) ;
2014-02-05 18:40:49 +03:00
}
2014-02-03 03:03:05 +03:00
}
}
2014-02-07 00:37:37 +03:00
2014-02-05 18:40:49 +03:00
# Generate relative path if requested
if ( $ bRelative )
2014-02-03 03:03:05 +03:00
{
2014-02-05 18:40:49 +03:00
my $ iCommonLen = common_prefix ( $ strSource , $ strDestination ) ;
if ( $ iCommonLen != 0 )
{
$ strSource = ( "../" x substr ( $ strDestination , $ iCommonLen ) =~ tr /\// / ) . substr ( $ strSource , $ iCommonLen ) ;
}
2014-02-03 03:03:05 +03:00
}
2014-02-05 18:40:49 +03:00
# Create the command
my $ strCommand = "ln" . ( ! $ bHard ? " -s" : "" ) . " ${strSource} ${strDestination}" ;
2014-02-07 00:37:37 +03:00
2014-02-04 03:03:17 +03:00
# Run remotely
2014-02-06 05:39:08 +03:00
if ( $ self - > is_remote ( $ strSourcePathType ) )
2014-02-04 03:03:17 +03:00
{
2014-02-13 21:54:43 +03:00
& log ( TRACE , "link_create: remote ${strSourcePathType} '${strCommand}'" ) ;
2014-02-04 03:03:17 +03:00
2014-02-06 05:39:08 +03:00
my $ oSSH = $ self - > remote_get ( $ strSourcePathType ) ;
2014-02-04 03:03:17 +03:00
$ oSSH - > system ( $ strCommand ) or confess & log ( "unable to create link from ${strSource} to ${strDestination}" ) ;
}
# Run locally
else
{
2014-02-13 21:54:43 +03:00
& log ( TRACE , "link_create: local '${strCommand}'" ) ;
2014-02-04 03:03:17 +03:00
system ( $ strCommand ) == 0 or confess & log ( "unable to create link from ${strSource} to ${strDestination}" ) ;
}
2014-02-03 03:03:05 +03:00
}
####################################################################################################################################
# PATH_CREATE
2014-02-04 03:03:17 +03:00
#
# Creates a path locally or remotely. Currently does not error if the path already exists. Also does not set permissions if the
# path aleady exists.
2014-02-03 03:03:05 +03:00
####################################################################################################################################
sub path_create
{
2014-02-06 05:39:08 +03:00
my $ self = shift ;
2014-02-03 03:03:05 +03:00
my $ strPathType = shift ;
my $ strPath = shift ;
my $ strPermission = shift ;
2014-02-07 00:37:37 +03:00
2014-02-04 03:03:17 +03:00
# If no permissions are given then use the default
2014-02-03 03:03:05 +03:00
if ( ! defined ( $ strPermission ) )
{
2014-02-06 05:39:08 +03:00
$ strPermission = $ self - > { strDefaultPathPermission } ;
2014-02-03 03:03:05 +03:00
}
# Get the path to create
my $ strPathCreate = $ strPath ;
if ( defined ( $ strPathType ) )
{
2014-02-06 05:39:08 +03:00
$ strPathCreate = $ self - > path_get ( $ strPathType , $ strPath ) ;
2014-02-03 03:03:05 +03:00
}
2014-02-04 03:03:17 +03:00
my $ strCommand = "mkdir -p -m ${strPermission} ${strPathCreate}" ;
# Run remotely
2014-02-06 05:39:08 +03:00
if ( $ self - > is_remote ( $ strPathType ) )
2014-02-04 03:03:17 +03:00
{
2014-02-13 21:54:43 +03:00
& log ( TRACE , "path_create: remote ${strPathType} '${strCommand}'" ) ;
2014-02-04 03:03:17 +03:00
2014-02-06 05:39:08 +03:00
my $ oSSH = $ self - > remote_get ( $ strPathType ) ;
2014-02-04 03:03:17 +03:00
$ oSSH - > system ( $ strCommand ) or confess & log ( "unable to create remote path ${strPathType}:${strPath}" ) ;
}
# Run locally
else
{
2014-02-13 21:54:43 +03:00
& log ( TRACE , "path_create: local '${strCommand}'" ) ;
2014-02-04 03:03:17 +03:00
system ( $ strCommand ) == 0 or confess & log ( ERROR , "unable to create path ${strPath}" ) ;
}
2014-02-03 03:03:05 +03:00
}
####################################################################################################################################
# IS_REMOTE
#
# Determine whether any operations are being performed remotely. If $strPathType is defined, the function will return true if that
# path is remote. If $strPathType is not defined, then function will return true if any path is remote.
####################################################################################################################################
sub is_remote
{
2014-02-06 05:39:08 +03:00
my $ self = shift ;
2014-02-03 03:03:05 +03:00
my $ strPathType = shift ;
2014-02-07 00:37:37 +03:00
2014-02-03 03:03:05 +03:00
# If the SSH object is defined then some paths are remote
2014-02-06 05:39:08 +03:00
if ( defined ( $ self - > { oDbSSH } ) || defined ( $ self - > { oBackupSSH } ) )
2014-02-03 03:03:05 +03:00
{
# If path type is not defined but the SSH object is, then some paths are remote
if ( ! defined ( $ strPathType ) )
{
return true ;
}
2014-02-07 00:37:37 +03:00
2014-02-03 03:03:05 +03:00
# If a host is defined for the path then it is remote
2014-02-06 05:39:08 +03:00
if ( defined ( $ self - > { strBackupHost } ) && $ self - > path_type_get ( $ strPathType ) eq PATH_BACKUP ||
defined ( $ self - > { strDbHost } ) && $ self - > path_type_get ( $ strPathType ) eq PATH_DB )
2014-02-03 03:03:05 +03:00
{
return true ;
}
}
return false ;
}
####################################################################################################################################
# REMOTE_GET
#
# Get remote SSH object depending on the path type.
####################################################################################################################################
sub remote_get
{
2014-02-06 05:39:08 +03:00
my $ self = shift ;
2014-02-03 03:03:05 +03:00
my $ strPathType = shift ;
2014-02-04 03:03:17 +03:00
# Get the db SSH object
2014-02-06 05:39:08 +03:00
if ( $ self - > path_type_get ( $ strPathType ) eq PATH_DB && defined ( $ self - > { oDbSSH } ) )
2014-02-03 03:03:05 +03:00
{
2014-02-06 05:39:08 +03:00
return $ self - > { oDbSSH } ;
2014-02-03 03:03:05 +03:00
}
2014-02-04 03:03:17 +03:00
# Get the backup SSH object
2014-02-06 05:39:08 +03:00
if ( $ self - > path_type_get ( $ strPathType ) eq PATH_BACKUP && defined ( $ self - > { oBackupSSH } ) )
2014-02-03 03:03:05 +03:00
{
2014-02-06 05:39:08 +03:00
return $ self - > { oBackupSSH }
2014-02-03 03:03:05 +03:00
}
2014-02-04 03:03:17 +03:00
# Error when no ssh object is found
2014-02-03 03:03:05 +03:00
confess & log ( ASSERT , "path type ${strPathType} does not have a defined ssh object" ) ;
}
2014-02-05 02:48:39 +03:00
####################################################################################################################################
# FILE_MOVE
#
# Moves a file locally or remotely.
####################################################################################################################################
sub file_move
{
2014-02-06 05:39:08 +03:00
my $ self = shift ;
2014-02-05 21:10:36 +03:00
my $ strSourcePathType = shift ;
2014-02-05 02:48:39 +03:00
my $ strSourceFile = shift ;
2014-02-05 21:10:36 +03:00
my $ strDestinationPathType = shift ;
2014-02-05 02:48:39 +03:00
my $ strDestinationFile = shift ;
2014-02-14 17:05:14 +03:00
my $ bPathCreate = shift ;
# if bPathCreate is not defined, default to true
$ bPathCreate = defined ( $ bPathCreate ) ? $ bPathCreate : true ;
2014-02-05 21:10:36 +03:00
2014-02-06 05:39:08 +03:00
if ( $ self - > path_type_get ( $ strSourcePathType ) ne $ self - > path_type_get ( $ strSourcePathType ) )
2014-02-05 21:10:36 +03:00
{
confess & log ( ASSERT , "source and destination path types must be equal" ) ;
}
2014-02-06 05:39:08 +03:00
my $ strSource = $ self - > path_get ( $ strSourcePathType , $ strSourceFile ) ;
my $ strDestination = $ self - > path_get ( $ strDestinationPathType , $ strDestinationFile ) ;
2014-02-05 02:48:39 +03:00
# If the destination path is backup and does not exist, create it
2014-02-14 17:05:14 +03:00
if ( $ bPathCreate && $ self - > path_type_get ( $ strDestinationPathType ) eq PATH_BACKUP )
2014-02-05 02:48:39 +03:00
{
2014-02-06 05:39:08 +03:00
$ self - > path_create ( PATH_BACKUP_ABSOLUTE , dirname ( $ strDestination ) ) ;
2014-02-05 02:48:39 +03:00
}
my $ strCommand = "mv ${strSource} ${strDestination}" ;
# Run remotely
2014-02-06 05:39:08 +03:00
if ( $ self - > is_remote ( $ strDestinationPathType ) )
2014-02-05 02:48:39 +03:00
{
2014-02-13 21:54:43 +03:00
& log ( TRACE , "file_move: remote ${strDestinationPathType} '${strCommand}'" ) ;
2014-02-05 02:48:39 +03:00
2014-02-06 05:39:08 +03:00
my $ oSSH = $ self - > remote_get ( $ strDestinationPathType ) ;
2014-02-05 21:10:36 +03:00
$ oSSH - > system ( $ strCommand )
or confess & log ( "unable to move remote ${strDestinationPathType}:${strSourceFile} to ${strDestinationFile}" ) ;
2014-02-05 02:48:39 +03:00
}
# Run locally
else
{
2014-02-13 21:54:43 +03:00
& log ( TRACE , "file_move: '${strCommand}'" ) ;
2014-02-05 02:48:39 +03:00
system ( $ strCommand ) == 0 or confess & log ( "unable to move local ${strSourceFile} to ${strDestinationFile}" ) ;
}
}
2014-02-03 03:03:05 +03:00
####################################################################################################################################
# FILE_COPY
####################################################################################################################################
sub file_copy
{
2014-02-06 05:39:08 +03:00
my $ self = shift ;
2014-02-03 03:03:05 +03:00
my $ strSourcePathType = shift ;
my $ strSourceFile = shift ;
my $ strDestinationPathType = shift ;
my $ strDestinationFile = shift ;
my $ bNoCompressionOverride = shift ;
2014-02-05 19:35:09 +03:00
my $ lModificationTime = shift ;
my $ strPermission = shift ;
2014-02-14 17:05:14 +03:00
my $ bPathCreate = shift ;
2014-02-18 23:42:51 +03:00
my $ bConfessCopyError = shift ;
2014-02-14 17:05:14 +03:00
# if bPathCreate is not defined, default to true
$ bPathCreate = defined ( $ bPathCreate ) ? $ bPathCreate : true ;
2014-02-18 23:42:51 +03:00
$ bConfessCopyError = defined ( $ bConfessCopyError ) ? $ bConfessCopyError : false ;
2014-02-05 19:35:09 +03:00
# Modification time and permissions cannot be set remotely
2014-02-06 05:39:08 +03:00
if ( ( defined ( $ lModificationTime ) || defined ( $ strPermission ) ) && $ self - > is_remote ( $ strDestinationPathType ) )
2014-02-05 19:35:09 +03:00
{
confess & log ( ASSERT , "modification time and permissions cannot be set on remote destination file" ) ;
}
2014-02-03 03:03:05 +03:00
# Generate source, destination and tmp filenames
2014-02-06 05:39:08 +03:00
my $ strSource = $ self - > path_get ( $ strSourcePathType , $ strSourceFile ) ;
my $ strDestination = $ self - > path_get ( $ strDestinationPathType , $ strDestinationFile ) ;
my $ strDestinationTmp = $ self - > path_get ( $ strDestinationPathType , $ strDestinationFile , true ) ;
2014-02-03 03:03:05 +03:00
# Is this already a compressed file?
2014-02-06 05:39:08 +03:00
my $ bAlreadyCompressed = $ strSource =~ "^.*\.$self->{strCompressExtension}\$" ;
2014-02-03 03:03:05 +03:00
2014-02-06 05:39:08 +03:00
if ( $ bAlreadyCompressed && $ strDestination !~ "^.*\.$self->{strCompressExtension}\$" )
2014-02-03 03:03:05 +03:00
{
2014-02-06 05:39:08 +03:00
$ strDestination . = ".$self->{strCompressExtension}" ;
2014-02-03 03:03:05 +03:00
}
# Does the file need compression?
2014-02-05 16:21:27 +03:00
my $ bCompress = ! ( ( defined ( $ bNoCompressionOverride ) && $ bNoCompressionOverride ) ||
2014-02-06 05:39:08 +03:00
( ! defined ( $ bNoCompressionOverride ) && $ self - > { bNoCompression } ) ) ;
2014-02-03 03:03:05 +03:00
# If the destination path is backup and does not exist, create it
2014-02-14 17:05:14 +03:00
if ( $ bPathCreate && $ self - > path_type_get ( $ strDestinationPathType ) eq PATH_BACKUP )
2014-02-03 03:03:05 +03:00
{
2014-02-06 05:39:08 +03:00
$ self - > path_create ( PATH_BACKUP_ABSOLUTE , dirname ( $ strDestination ) ) ;
2014-02-03 03:03:05 +03:00
}
2014-02-05 16:21:27 +03:00
# Generate the command string depending on compression/decompression/cat
2014-02-06 05:39:08 +03:00
my $ strCommand = $ self - > { strCommandCat } ;
2014-02-05 16:21:27 +03:00
2014-02-15 22:18:15 +03:00
if ( ! $ bAlreadyCompressed && $ bCompress )
2014-02-05 16:21:27 +03:00
{
2014-02-06 05:39:08 +03:00
$ strCommand = $ self - > { strCommandCompress } ;
2014-02-05 16:21:27 +03:00
$ strDestination . = ".gz" ;
}
elsif ( $ bAlreadyCompressed && ! $ bCompress )
{
2014-02-06 05:39:08 +03:00
$ strCommand = $ self - > { strCommandDecompress } ;
$ strDestination = substr ( $ strDestination , 0 , length ( $ strDestination ) - length ( $ self - > { strCompressExtension } ) - 1 ) ;
2014-02-05 16:21:27 +03:00
}
2014-02-03 03:03:05 +03:00
$ strCommand =~ s/\%file\%/${strSource}/g ;
# If this command is remote on only one side
2014-02-06 05:39:08 +03:00
if ( $ self - > is_remote ( $ strSourcePathType ) && ! $ self - > is_remote ( $ strDestinationPathType ) ||
! $ self - > is_remote ( $ strSourcePathType ) && $ self - > is_remote ( $ strDestinationPathType ) )
2014-02-03 03:03:05 +03:00
{
# Else if the source is remote
2014-02-06 05:39:08 +03:00
if ( $ self - > is_remote ( $ strSourcePathType ) )
2014-02-03 03:03:05 +03:00
{
2014-02-13 21:54:43 +03:00
& log ( TRACE , "file_copy: remote ${strSource} to local ${strDestination}" ) ;
2014-02-03 03:03:05 +03:00
# Open the destination file for writing (will be streamed from the ssh session)
my $ hFile ;
open ( $ hFile , ">" , $ strDestinationTmp ) or confess & log ( ERROR , "cannot open ${strDestination}" ) ;
# Execute the command through ssh
2014-02-06 05:39:08 +03:00
my $ oSSH = $ self - > remote_get ( $ strSourcePathType ) ;
2014-02-18 23:42:51 +03:00
unless ( $ oSSH - > system ( { stdout_fh = > $ hFile , stderr_discard = > true } , $ strCommand ) )
{
close ( $ hFile ) or confess & log ( ERROR , "cannot close file ${strDestinationTmp}" ) ;
my $ strResult = "unable to execute ssh '${strCommand}'" ;
$ bConfessCopyError ? confess & log ( ERROR , $ strResult ) : return false ;
}
2014-02-03 03:03:05 +03:00
# Close the destination file handle
2014-02-18 23:42:51 +03:00
close ( $ hFile ) or confess & log ( ERROR , "cannot close file ${strDestinationTmp}" ) ;
2014-02-03 03:03:05 +03:00
}
# Else if the destination is remote
2014-02-06 05:39:08 +03:00
elsif ( $ self - > is_remote ( $ strDestinationPathType ) )
2014-02-03 03:03:05 +03:00
{
2014-02-13 21:54:43 +03:00
& log ( TRACE , "file_copy: local ${strSource} ($strCommand) to remote ${strDestination}" ) ;
2014-02-03 03:03:05 +03:00
# Open the input command as a stream
my $ hOut ;
my $ pId = open3 ( undef , $ hOut , undef , $ strCommand ) or confess ( ERROR , "unable to execute '${strCommand}'" ) ;
# Execute the command though ssh
2014-02-06 05:39:08 +03:00
my $ oSSH = $ self - > remote_get ( $ strDestinationPathType ) ;
2014-02-03 03:03:05 +03:00
$ oSSH - > system ( { stdin_fh = > $ hOut } , "cat > ${strDestinationTmp}" ) or confess & log ( ERROR , "unable to execute ssh 'cat'" ) ;
# Wait for the stream process to finish
waitpid ( $ pId , 0 ) ;
my $ iExitStatus = $ { ^ CHILD_ERROR_NATIVE } >> 8 ;
if ( $ iExitStatus != 0 )
{
2014-02-18 23:42:51 +03:00
my $ strResult = "command '${strCommand}' returned " . $ iExitStatus ;
$ bConfessCopyError ? confess & log ( ERROR , $ strResult ) : return false ;
2014-02-03 03:03:05 +03:00
}
}
}
# If the source and destination are both remote but not the same remote
2014-02-06 05:39:08 +03:00
elsif ( $ self - > is_remote ( $ strSourcePathType ) && $ self - > is_remote ( $ strDestinationPathType ) &&
$ self - > path_type_get ( $ strSourcePathType ) ne $ self - > path_type_get ( $ strDestinationPathType ) )
2014-02-03 03:03:05 +03:00
{
2014-02-13 21:54:43 +03:00
& log ( TRACE , "file_copy: remote ${strSource} to remote ${strDestination}" ) ;
2014-02-03 03:03:05 +03:00
confess & log ( ASSERT , "remote source and destination not supported" ) ;
}
# Else this is a local command or remote where both sides are the same remote
else
{
# Complete the command by redirecting to the destination tmp file
$ strCommand . = " > ${strDestinationTmp}" ;
2014-02-06 06:26:10 +03:00
if ( $ self - > is_remote ( $ strSourcePathType ) )
2014-02-03 03:03:05 +03:00
{
2014-02-13 21:54:43 +03:00
& log ( TRACE , "file_copy: remote ${strSourcePathType} '${strCommand}'" ) ;
2014-02-03 03:03:05 +03:00
2014-02-06 05:39:08 +03:00
my $ oSSH = $ self - > remote_get ( $ strSourcePathType ) ;
2014-02-18 23:42:51 +03:00
unless ( $ oSSH - > system ( { stderr_discard = > true } , $ strCommand ) )
{
my $ strResult = "unable to execute remote command ${strCommand}:" . oSSH - > error ;
$ bConfessCopyError ? confess & log ( ERROR , $ strResult ) : return false ;
}
2014-02-03 03:03:05 +03:00
}
else
{
2014-02-13 21:54:43 +03:00
& log ( TRACE , "file_copy: local '${strCommand}'" ) ;
2014-02-18 23:42:51 +03:00
unless ( system ( $ strCommand ) == 0 )
{
my $ strResult = "unable to copy local ${strSource} to local ${strDestinationTmp}" ;
$ bConfessCopyError ? confess & log ( ERROR , $ strResult ) : return false ;
}
2014-02-03 03:03:05 +03:00
}
}
2014-02-05 19:35:09 +03:00
# Set the file permission if required (this only works locally for now)
if ( defined ( $ strPermission ) )
{
2014-02-13 21:54:43 +03:00
& log ( TRACE , "file_copy: chmod ${strPermission}" ) ;
2014-02-05 19:35:09 +03:00
system ( "chmod ${strPermission} ${strDestinationTmp}" ) == 0
or confess & log ( ERROR , "unable to set permissions for local ${strDestinationTmp}" ) ;
}
2014-02-07 00:37:37 +03:00
2014-02-05 19:35:09 +03:00
# Set the file modification time if required (this only works locally for now)
if ( defined ( $ lModificationTime ) )
{
2014-02-13 21:54:43 +03:00
& log ( TRACE , "file_copy: time ${lModificationTime}" ) ;
2014-02-05 19:35:09 +03:00
utime ( $ lModificationTime , $ lModificationTime , $ strDestinationTmp )
or confess & log ( ERROR , "unable to set time for local ${strDestinationTmp}" ) ;
}
2014-02-07 00:37:37 +03:00
2014-02-03 03:03:05 +03:00
# Move the file from tmp to final destination
2014-02-06 05:39:08 +03:00
$ self - > file_move ( $ self - > path_type_get ( $ strSourcePathType ) . ":absolute" , $ strDestinationTmp ,
2014-02-14 17:05:14 +03:00
$ self - > path_type_get ( $ strDestinationPathType ) . ":absolute" , $ strDestination , $ bPathCreate ) ;
2014-02-18 23:42:51 +03:00
return true ;
2014-02-03 03:03:05 +03:00
}
####################################################################################################################################
# FILE_HASH_GET
####################################################################################################################################
sub file_hash_get
{
2014-02-06 05:39:08 +03:00
my $ self = shift ;
2014-02-03 03:03:05 +03:00
my $ strPathType = shift ;
my $ strFile = shift ;
2014-02-07 00:37:37 +03:00
2014-02-04 03:03:17 +03:00
# For now this operation is not supported remotely. Not currently needed.
2014-02-06 05:39:08 +03:00
if ( $ self - > is_remote ( $ strPathType ) )
2014-02-04 03:03:17 +03:00
{
confess & log ( ASSERT , "remote operation not supported" ) ;
}
2014-02-07 00:37:37 +03:00
2014-02-06 05:39:08 +03:00
if ( ! defined ( $ self - > { strCommandChecksum } ) )
2014-02-03 03:03:05 +03:00
{
confess & log ( ASSERT , "\$strCommandChecksum not defined" ) ;
}
2014-02-07 00:37:37 +03:00
2014-02-06 05:39:08 +03:00
my $ strPath = $ self - > path_get ( $ strPathType , $ strFile ) ;
2014-02-03 03:03:05 +03:00
my $ strCommand ;
2014-02-07 00:37:37 +03:00
2014-02-03 03:03:05 +03:00
if ( - e $ strPath )
{
2014-02-06 05:39:08 +03:00
$ strCommand = $ self - > { strCommandChecksum } ;
2014-02-05 16:21:27 +03:00
$ strCommand =~ s/\%file\%/${strPath}/g ;
2014-02-03 03:03:05 +03:00
}
2014-02-06 05:39:08 +03:00
elsif ( - e $ strPath . ".$self->{strCompressExtension}" )
2014-02-03 03:03:05 +03:00
{
2014-02-06 05:39:08 +03:00
$ strCommand = $ self - > { strCommandDecompress } ;
2014-02-03 03:03:05 +03:00
$ strCommand =~ s/\%file\%/${strPath}/g ;
2014-02-06 05:39:08 +03:00
$ strCommand . = " | " . $ self - > { strCommandChecksum } ;
2014-02-03 03:03:05 +03:00
$ strCommand =~ s/\%file\%//g ;
}
else
{
2014-02-06 05:39:08 +03:00
confess & log ( ASSERT , "unable to find $strPath(.$self->{strCompressExtension}) for checksum" ) ;
2014-02-03 03:03:05 +03:00
}
2014-02-07 00:37:37 +03:00
2014-02-03 03:03:05 +03:00
return trim ( capture ( $ strCommand ) ) or confess & log ( ERROR , "unable to checksum ${strPath}" ) ;
}
2014-02-15 22:18:15 +03:00
####################################################################################################################################
# FILE_COMPRESS
####################################################################################################################################
sub file_compress
{
my $ self = shift ;
my $ strPathType = shift ;
my $ strFile = shift ;
# For now this operation is not supported remotely. Not currently needed.
if ( $ self - > is_remote ( $ strPathType ) )
{
confess & log ( ASSERT , "remote operation not supported" ) ;
}
if ( ! defined ( $ self - > { strCommandCompress } ) )
{
confess & log ( ASSERT , "\$strCommandChecksum not defined" ) ;
}
my $ strPath = $ self - > path_get ( $ strPathType , $ strFile ) ;
# Build the command
my $ strCommand = $ self - > { strCommandCompress } ;
$ strCommand =~ s/\%file\%/${strPath}/g ;
$ strCommand =~ s/\ \-\-stdout//g ;
system ( $ strCommand ) == 0 or confess & log ( ERROR , "unable to compress ${strPath}: ${strCommand}" ) ;
}
2014-02-03 03:03:05 +03:00
####################################################################################################################################
# FILE_LIST_GET
####################################################################################################################################
sub file_list_get
{
2014-02-06 05:39:08 +03:00
my $ self = shift ;
2014-02-04 03:03:17 +03:00
my $ strPathType = shift ;
2014-02-03 03:03:05 +03:00
my $ strPath = shift ;
my $ strExpression = shift ;
my $ strSortOrder = shift ;
2014-02-04 03:03:17 +03:00
# For now this operation is not supported remotely. Not currently needed.
2014-02-06 05:39:08 +03:00
if ( $ self - > is_remote ( $ strPathType ) )
2014-02-04 03:03:17 +03:00
{
confess & log ( ASSERT , "remote operation not supported" ) ;
}
2014-02-07 00:37:37 +03:00
2014-02-06 05:39:08 +03:00
my $ strPathList = $ self - > path_get ( $ strPathType , $ strPath ) ;
2014-02-03 03:03:05 +03:00
my $ hDir ;
2014-02-07 00:37:37 +03:00
2014-02-04 03:03:17 +03:00
opendir $ hDir , $ strPathList or confess & log ( ERROR , "unable to open path ${strPathList}" ) ;
my @ stryFileAll = readdir $ hDir or confess & log ( ERROR , "unable to get files for path ${strPathList}, expression ${strExpression}" ) ;
2014-02-03 03:03:05 +03:00
close $ hDir ;
2014-02-07 00:37:37 +03:00
2014-02-03 03:03:05 +03:00
my @ stryFile ;
if ( @ stryFileAll )
{
@ stryFile = grep ( /$strExpression/i , @ stryFileAll )
}
2014-02-07 00:37:37 +03:00
2014-02-03 03:03:05 +03:00
if ( @ stryFile )
{
if ( defined ( $ strSortOrder ) && $ strSortOrder eq "reverse" )
{
return sort { $ b cmp $ a } @ stryFile ;
}
else
{
return sort @ stryFile ;
}
}
2014-02-07 00:37:37 +03:00
2014-02-03 03:03:05 +03:00
return @ stryFile ;
}
2014-02-18 23:42:51 +03:00
####################################################################################################################################
# FILE_EXISTS
####################################################################################################################################
sub file_exists
{
my $ self = shift ;
my $ strPathType = shift ;
my $ strPath = shift ;
# Get the root path for the manifest
my $ strPathExists = $ self - > path_get ( $ strPathType , $ strPath ) ;
# Builds the exists command
my $ strCommand = "ls ${strPathExists}" ;
# Run the file exists command
my $ strExists = "" ;
# Run remotely
if ( $ self - > is_remote ( $ strPathType ) )
{
& log ( TRACE , "file_exists: remote ${strPathType}:${strPathExists}" ) ;
my $ oSSH = $ self - > remote_get ( $ strPathType ) ;
$ strExists = $ oSSH - > capture ( { stderr_discard = > true } , $ strCommand ) ;
}
# Run locally
else
{
& log ( TRACE , "file_exists: local ${strPathType}:${strPathExists}" ) ;
$ strExists = capture ( $ strCommand ) ;
}
# If the return from ls eq strPathExists then true
return ( $ strExists eq $ strPathExists ) ;
}
####################################################################################################################################
# FILE_REMOVE
####################################################################################################################################
sub file_remove
{
my $ self = shift ;
my $ strPathType = shift ;
my $ strPath = shift ;
my $ bTemp = shift ;
my $ bErrorIfNotExists = shift ;
if ( ! defined ( $ bErrorIfNotExists ) )
{
$ bErrorIfNotExists = false ;
}
# Get the root path for the manifest
my $ strPathRemove = $ self - > path_get ( $ strPathType , $ strPath , $ bTemp ) ;
# Builds the exists command
my $ strCommand = "rm -f ${strPathRemove}" ;
# Run remotely
if ( $ self - > is_remote ( $ strPathType ) )
{
& log ( TRACE , "file_remove: remote ${strPathType}:${strPathRemove}" ) ;
my $ oSSH = $ self - > remote_get ( $ strPathType ) ;
$ oSSH - > system ( { stderr_discard = > true } , $ strCommand ) or $ bErrorIfNotExists ? confess & log ( ERROR , "unable to remove remote ${strPathType}:${strPathRemove}" ) : true ;
}
# Run locally
else
{
& log ( TRACE , "file_exists: local ${strPathType}:${strPathRemove}" ) ;
system ( $ strCommand ) == 0 or $ bErrorIfNotExists ? confess & log ( ERROR , "unable to remove local ${strPathType}:${strPathRemove}" ) : true ;
}
}
2014-02-03 03:03:05 +03:00
####################################################################################################################################
# MANIFEST_GET
#
# Builds a path/file manifest starting with the base path and including all subpaths. The manifest contains all the information
# needed to perform a backup or a delta with a previous backup.
####################################################################################################################################
sub manifest_get
{
2014-02-06 05:39:08 +03:00
my $ self = shift ;
2014-02-03 03:03:05 +03:00
my $ strPathType = shift ;
my $ strPath = shift ;
2014-02-13 21:54:43 +03:00
& log ( TRACE , "manifest: " . $ self - > { strCommandManifest } ) ;
2014-02-03 03:03:05 +03:00
# Get the root path for the manifest
2014-02-06 05:39:08 +03:00
my $ strPathManifest = $ self - > path_get ( $ strPathType , $ strPath ) ;
2014-02-03 03:03:05 +03:00
# Builds the manifest command
2014-02-06 05:39:08 +03:00
my $ strCommand = $ self - > { strCommandManifest } ;
2014-02-03 03:03:05 +03:00
$ strCommand =~ s/\%path\%/${strPathManifest}/g ;
# Run the manifest command
my $ strManifest ;
# Run remotely
2014-02-06 05:39:08 +03:00
if ( $ self - > is_remote ( $ strPathType ) )
2014-02-03 03:03:05 +03:00
{
2014-02-13 21:54:43 +03:00
& log ( TRACE , "manifest_get: remote ${strPathType}:${strPathManifest}" ) ;
2014-02-03 03:03:05 +03:00
2014-02-06 05:39:08 +03:00
my $ oSSH = $ self - > remote_get ( $ strPathType ) ;
2014-02-03 03:03:05 +03:00
$ strManifest = $ oSSH - > capture ( $ strCommand ) or confess & log ( ERROR , "unable to execute remote command '${strCommand}'" ) ;
}
# Run locally
else
{
2014-02-13 21:54:43 +03:00
& log ( TRACE , "manifest_get: local ${strPathType}:${strPathManifest}" ) ;
2014-02-03 03:03:05 +03:00
$ strManifest = capture ( $ strCommand ) or confess & log ( ERROR , "unable to execute local command '${strCommand}'" ) ;
}
# Load the manifest into a hash
return data_hash_build ( "name\ttype\tuser\tgroup\tpermission\tmodification_time\tinode\tsize\tlink_destination\n" .
$ strManifest , "\t" , "." ) ;
}
2014-02-06 05:39:08 +03:00
no Moose ;
__PACKAGE__ - > meta - > make_immutable ;