1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2024-12-14 10:13:05 +02:00
pgbackrest/pg_backrest_file.pm

1415 lines
47 KiB
Perl
Raw Normal View History

2014-02-03 03:03:05 +03:00
####################################################################################################################################
# FILE MODULE
####################################################################################################################################
package pg_backrest_file;
use threads;
2014-02-03 03:03:05 +03:00
use strict;
use warnings;
use Carp;
use Moose;
2014-02-03 03:03:05 +03:00
use Net::OpenSSH;
use IPC::Open3;
use File::Basename;
use IPC::System::Simple qw(capture);
2014-06-02 00:23:33 +03:00
use Digest::SHA;
use File::stat;
use Fcntl ':mode';
2014-06-03 00:48:07 +03:00
use IO::Compress::Gzip qw(gzip $GzipError);
use IO::Uncompress::Gunzip qw(gunzip $GunzipError);
2014-06-06 05:42:47 +03:00
use IO::String;
2014-02-03 03:03:05 +03:00
use lib dirname($0);
use pg_backrest_utility;
use Exporter qw(import);
our @EXPORT = qw(PATH_ABSOLUTE PATH_DB PATH_DB_ABSOLUTE PATH_BACKUP PATH_BACKUP_ABSOLUTE
2014-06-06 05:42:47 +03:00
PATH_BACKUP_CLUSTERPATH_BACKUP_TMP PATH_BACKUP_ARCHIVE
COMMAND_ERR_FILE_MISSING COMMAND_ERR_FILE_READ COMMAND_ERR_FILE_MOVE COMMAND_ERR_FILE_TYPE
COMMAND_ERR_LINK_READ COMMAND_ERR_PATH_MISSING COMMAND_ERR_PATH_CREATE COMMAND_ERR_PARAM);
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-06-02 00:23:33 +03:00
has strCommand => (is => 'bare');
2014-02-04 04:48:02 +03:00
2014-04-28 16:13:25 +03:00
# Lock path
has strLockPath => (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-06-06 05:42:47 +03:00
has bCompress => (is => 'bare');
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-06-02 00:23:33 +03:00
####################################################################################################################################
# COMMAND Error Constants
####################################################################################################################################
use constant
{
COMMAND_ERR_FILE_MISSING => 1,
COMMAND_ERR_FILE_READ => 2,
2014-06-04 02:03:03 +03:00
COMMAND_ERR_FILE_MOVE => 3,
COMMAND_ERR_FILE_TYPE => 4,
COMMAND_ERR_LINK_READ => 5,
COMMAND_ERR_PATH_MISSING => 6,
2014-06-06 05:42:47 +03:00
COMMAND_ERR_PATH_CREATE => 7,
COMMAND_ERR_PARAM => 8
2014-06-02 00:23:33 +03:00
};
2014-04-28 16:13:25 +03:00
####################################################################################################################################
# PATH_GET Constants
####################################################################################################################################
use constant
{
2014-06-02 00:23:33 +03:00
PATH_ABSOLUTE => 'absolute',
2014-04-28 16:13:25 +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',
PATH_LOCK_ERR => 'lock:err'
};
####################################################################################################################################
# File copy block size constant
####################################################################################################################################
use constant
{
BLOCK_SIZE => 8192
};
2014-02-07 00:37:37 +03:00
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub BUILD
2014-02-03 03:03:05 +03:00
{
2014-02-06 05:39:08 +03:00
my $self = shift;
2014-06-04 18:58:30 +03:00
2014-02-03 22:50:23 +03:00
# Make sure the backup path is defined
2014-06-02 00:23:33 +03:00
if (defined($self->{strBackupPath}))
2014-02-03 22:50:23 +03:00
{
2014-06-02 00:23:33 +03:00
# Create the backup cluster path
$self->{strBackupClusterPath} = $self->{strBackupPath} . "/" . $self->{strStanza};
2014-02-03 22:50:23 +03:00
}
2014-02-06 05:39:08 +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
{
if (defined($self->{strBackupHost}) && defined($self->{strDbHost}))
{
confess &log(ASSERT, "backup and db hosts cannot both be remote");
}
2014-06-04 18:58:30 +03:00
2014-04-01 15:59:09 +03:00
my $strOptionSSHRequestTTY = "RequestTTY=yes";
my $strOptionSSHCompression = "Compression=no";
2014-02-17 03:01:06 +03:00
2014-06-06 05:42:47 +03:00
# if ($self->{bNoCompression})
# {
# $strOptionSSHCompression = "Compression=yes";
# }
2014-02-17 03:01:06 +03:00
# Connect SSH object if backup host is defined
if (!defined($self->{oBackupSSH}) && defined($self->{strBackupHost}))
2014-02-17 03:01:06 +03:00
{
&log(TRACE, "connecting to backup ssh host " . $self->{strBackupHost});
2014-06-04 18:58:30 +03:00
2014-04-01 15:59:09 +03:00
$self->{oBackupSSH} = Net::OpenSSH->new($self->{strBackupHost}, timeout => 300, user => $self->{strBackupUser},
master_opts => [-o => $strOptionSSHCompression, -o => $strOptionSSHRequestTTY]);
2014-02-17 03:01:06 +03:00
$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->{oDbSSH}) && defined($self->{strDbHost}))
2014-02-17 03:01:06 +03:00
{
&log(TRACE, "connecting to database ssh host $self->{strDbHost}");
2014-04-01 15:59:09 +03:00
$self->{oDbSSH} = Net::OpenSSH->new($self->{strDbHost}, timeout => 300, user => $self->{strDbUser},
master_opts => [-o => $strOptionSSHCompression, -o => $strOptionSSHRequestTTY]);
2014-02-17 03:01:06 +03:00
$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},
2014-06-02 00:23:33 +03:00
strCommand => $self->{strCommand},
2014-02-11 23:31:16 +03:00
strDbUser => $self->{strDbUser},
strDbHost => $self->{strDbHost},
strBackupUser => $self->{strBackupUser},
strBackupHost => $self->{strBackupHost},
strBackupPath => $self->{strBackupPath},
strBackupClusterPath => $self->{strBackupClusterPath},
2014-06-06 05:42:47 +03:00
bCompress => $self->{bCompress},
2014-02-11 23:31:16 +03:00
strStanza => $self->{strStanza},
2014-04-28 16:13:25 +03:00
iThreadIdx => $iThreadIdx,
strLockPath => $self->{strLockPath}
2014-02-11 23:31:16 +03:00
);
}
2014-02-03 03:03:05 +03:00
####################################################################################################################################
2014-04-28 16:13:25 +03:00
# ERROR_GET
2014-02-03 03:03:05 +03:00
####################################################################################################################################
# sub error_get
# {
# my $self = shift;
2014-06-06 05:42:47 +03:00
#
# my $strErrorFile = $self->path_get(PATH_LOCK_ERR, "file");
2014-06-06 05:42:47 +03:00
#
# open my $hFile, '<', $strErrorFile or return "error opening ${strErrorFile} to read STDERR output";
2014-06-06 05:42:47 +03:00
#
# my $strError = do {local $/; <$hFile>};
# close $hFile;
2014-06-06 05:42:47 +03:00
#
# return trim($strError);
# }
2014-02-03 03:03:05 +03:00
2014-05-27 16:00:24 +03:00
####################################################################################################################################
# ERROR_CLEAR
####################################################################################################################################
# sub error_clear
# {
# my $self = shift;
2014-06-06 05:42:47 +03:00
#
# unlink($self->path_get(PATH_LOCK_ERR, "file"));
# }
2014-05-27 16:00:24 +03:00
2014-04-28 16:13:25 +03:00
####################################################################################################################################
2014-06-04 02:03:03 +03:00
# PATH_TYPE_GET
2014-04-28 16:13:25 +03:00
####################################################################################################################################
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-06-04 05:02:56 +03:00
# If absolute type
2014-06-04 02:03:03 +03:00
if ($strType eq PATH_ABSOLUTE)
{
return PATH_ABSOLUTE;
}
2014-06-04 05:02:56 +03:00
# If db type
2014-06-04 02:03:03 +03:00
elsif ($strType =~ /^db(\:.*){0,1}/)
2014-02-03 03:03:05 +03:00
{
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}'");
}
2014-06-04 02:03:03 +03:00
####################################################################################################################################
# PATH_GET
####################################################################################################################################
2014-02-03 03:03:05 +03:00
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-06-04 02:03:03 +03:00
# Make sure that any absolute path starts with /, otherwise it will actually be relative
my $bAbsolute = $strType =~ /.*absolute.*/;
if ($bAbsolute && $strFile !~ /^\/.*/)
2014-02-03 03:03:05 +03:00
{
2014-06-04 02:03:03 +03:00
confess &log(ASSERT, "absolute path ${strType}:${strFile} must start with /");
2014-02-03 03:03:05 +03:00
}
2014-06-04 02:03:03 +03:00
# Only allow temp files for PATH_BACKUP_ARCHIVE and PATH_BACKUP_TMP and any absolute path
$bTemp = defined($bTemp) ? $bTemp : false;
if ($bTemp && !($strType eq PATH_BACKUP_ARCHIVE || $strType eq PATH_BACKUP_TMP || $bAbsolute))
2014-06-02 00:23:33 +03:00
{
2014-06-04 02:03:03 +03:00
confess &log(ASSERT, "temp file not supported on path " . $strType);
2014-06-02 00:23:33 +03:00
}
2014-06-04 02:03:03 +03:00
# Get absolute path
if ($bAbsolute)
2014-02-03 03:03:05 +03:00
{
if (defined($bTemp) && $bTemp)
{
return $strFile . ".backrest.tmp";
}
2014-02-03 03:03:05 +03:00
return $strFile;
}
2014-02-05 23:56:05 +03:00
2014-06-04 02:03:03 +03:00
# Make sure the base backup path is defined (since all other path types are backup)
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 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
if (!defined($self->{strStanza}))
2014-02-05 23:56:05 +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-05-13 18:23:15 +03:00
# Get the lock error path
2014-04-28 16:13:25 +03:00
if ($strType eq PATH_LOCK_ERR)
{
2014-05-27 16:00:24 +03:00
my $strTempPath = $self->{strLockPath};
2014-06-04 18:58:30 +03:00
2014-05-27 16:00:24 +03:00
if (!defined($strTempPath))
{
return undef;
}
2014-04-28 16:13:25 +03:00
return ${strTempPath} . (defined($strFile) ? "/${strFile}" .
(defined($self->{iThreadIdx}) ? ".$self->{iThreadIdx}" : "") . ".err" : "");
}
2014-05-13 18:23:15 +03:00
# Get the backup tmp path
2014-02-05 23:56:05 +03:00
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-06-04 02:03:03 +03:00
if ($bTemp)
2014-02-05 23:56:05 +03:00
{
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
{
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-06-04 02:03:03 +03:00
if ($bTemp)
2014-02-13 23:26:07 +03:00
{
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)
{
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}'");
}
####################################################################################################################################
# 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
{
my $self = shift;
my $strPathType = shift;
# If the SSH object is defined then some paths are remote
if (defined($self->{oDbSSH}) || defined($self->{oBackupSSH}))
{
# If path type is not defined but the SSH object is, then some paths are remote
if (!defined($strPathType))
{
return true;
}
# If a host is defined for the path then it is remote
if (defined($self->{strBackupHost}) && $self->path_type_get($strPathType) eq PATH_BACKUP ||
defined($self->{strDbHost}) && $self->path_type_get($strPathType) eq PATH_DB)
{
return true;
}
}
return false;
}
####################################################################################################################################
# REMOTE_GET
#
# Get remote SSH object depending on the path type.
####################################################################################################################################
sub remote_get
{
my $self = shift;
my $strPathType = shift;
2014-06-04 18:58:30 +03:00
# Get the db SSH object
if ($self->path_type_get($strPathType) eq PATH_DB && defined($self->{oDbSSH}))
{
return $self->{oDbSSH};
}
# Get the backup SSH object
if ($self->path_type_get($strPathType) eq PATH_BACKUP && defined($self->{oBackupSSH}))
{
return $self->{oBackupSSH}
}
# Error when no ssh object is found
confess &log(ASSERT, "path type ${strPathType} does not have a defined ssh object");
}
2014-02-03 03:03:05 +03:00
####################################################################################################################################
2014-06-04 05:02:56 +03:00
# LINK_CREATE !!! NEEDS TO BE CONVERTED
2014-02-03 03:03:05 +03:00
####################################################################################################################################
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;
my $bPathCreate = shift;
2014-02-07 00:37:37 +03:00
# if bHard is not defined default to false
$bHard = defined($bHard) ? $bHard : false;
2014-02-07 00:37:37 +03:00
# if bRelative is not defined or bHard is true, default to false
$bRelative = !defined($bRelative) || $bHard ? false : $bRelative;
# if bPathCreate is not defined, default to true
$bPathCreate = defined($bPathCreate) ? $bPathCreate : true;
2014-06-04 18:58:30 +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");
}
# 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
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
{
# 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-03 03:03:05 +03:00
}
}
2014-02-07 00:37:37 +03:00
# Generate relative path if requested
if ($bRelative)
2014-02-03 03:03:05 +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
}
# 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
# Setup standard variables
my $strErrorPrefix = "File->path_create";
my $bRemote = $self->is_remote($strPathType);
my $strPathOp = $self->path_get($strPathType, $strPath);
2014-02-03 03:03:05 +03:00
2014-06-04 18:58:30 +03:00
&log(TRACE, "${strErrorPrefix}: " . ($bRemote ? "remote" : "local") . " ${strPathType}:${strPath}, " .
"permission " . (defined($strPermission) ? $strPermission : "[undef]"));
2014-02-03 03:03:05 +03:00
if ($bRemote)
2014-02-04 03:03:17 +03:00
{
# Run remotely
2014-02-06 05:39:08 +03:00
my $oSSH = $self->remote_get($strPathType);
my $strOutput = $oSSH->capture($self->{strCommand} .
(defined($strPermission) ? " --permission=${strPermission}" : "") .
" path_create ${strPath}");
2014-02-07 00:37:37 +03:00
# Capture any errors
if ($oSSH->error)
2014-02-03 03:03:05 +03:00
{
confess &log(ERROR, "${strErrorPrefix} remote: " . (defined($strOutput) ? $strOutput : $oSSH->error));
2014-02-03 03:03:05 +03:00
}
}
else
2014-02-03 03:03:05 +03:00
{
# Attempt the create the directory
if (!mkdir($strPathOp, oct(defined($strPermission) ? $strPermission : $self->{strDefaultPathPermission})))
{
# Capture the error
my $strError = "${strPath} could not be created: " . $!;
2014-02-03 03:03:05 +03:00
# If running on command line the return directly
if ($strPathType eq PATH_ABSOLUTE)
{
print $strError;
exit COMMAND_ERR_PATH_CREATE;
}
2014-02-03 03:03:05 +03:00
# Error the normal way
confess &log(ERROR, "${strErrorPrefix}: " . $strError);
}
}
2014-02-03 03:03:05 +03:00
}
2014-02-05 02:48:39 +03:00
####################################################################################################################################
2014-06-04 02:03:03 +03:00
# MOVE
2014-02-05 02:48:39 +03:00
#
# Moves a file locally or remotely.
####################################################################################################################################
2014-06-04 02:03:03 +03:00
sub move
2014-02-05 02:48:39 +03:00
{
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-06-04 02:03:03 +03:00
my $bDestinationPathCreate = shift;
2014-06-04 02:03:03 +03:00
# Get the root path for the file list
my $strErrorPrefix = "File->move";
my $bRemote = $self->is_remote($strSourcePathType);
$bDestinationPathCreate = defined($bDestinationPathCreate) ? $bDestinationPathCreate : true;
2014-02-05 21:10:36 +03:00
2014-06-04 02:03:03 +03:00
&log(TRACE, "${strErrorPrefix}: " . ($bRemote ? "remote" : "local") .
" ${strSourcePathType}" . (defined($strSourceFile) ? ":${strSourceFile}" : "") .
" to ${strDestinationPathType}" . (defined($strDestinationFile) ? ":${strDestinationFile}" : "") .
", dest_path_create = " . ($bDestinationPathCreate ? "true" : "false"));
# Get source and desination files
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-06-04 02:03:03 +03:00
my $strPathOpSource = $self->path_get($strSourcePathType, $strSourceFile);
my $strPathOpDestination = $self->path_get($strDestinationPathType, $strDestinationFile);
2014-02-05 02:48:39 +03:00
# Run remotely
2014-06-04 02:03:03 +03:00
if ($bRemote)
2014-02-05 02:48:39 +03:00
{
2014-06-04 02:03:03 +03:00
my $strCommand = $self->{strCommand} .
($bDestinationPathCreate ? " --dest-path-create" : "") .
" move ${strPathOpSource} ${strPathOpDestination}";
# Run via SSH
my $oSSH = $self->remote_get($strSourcePathType);
my $strOutput = $oSSH->capture($strCommand);
2014-02-05 02:48:39 +03:00
2014-06-04 02:03:03 +03:00
# Handle any errors
if ($oSSH->error)
{
confess &log(ERROR, "${strErrorPrefix} remote (${strCommand}): " . (defined($strOutput) ? $strOutput : $oSSH->error));
}
2014-02-05 02:48:39 +03:00
}
# Run locally
else
{
2014-06-04 02:03:03 +03:00
# If the destination path does not exist, create it
unless (-e dirname($strPathOpDestination))
{
if ($bDestinationPathCreate)
{
$self->path_create($strDestinationPathType, dirname($strDestinationFile));
}
else
{
my $strError = "destination " . dirname($strPathOpDestination) . " does not exist";
if ($strSourcePathType eq PATH_ABSOLUTE)
{
print $strError;
exit (COMMAND_ERR_PATH_MISSING);
}
2014-02-05 02:48:39 +03:00
2014-06-04 02:03:03 +03:00
confess &log(ERROR, "${strErrorPrefix}: " . $strError);
}
}
2014-06-04 18:58:30 +03:00
2014-06-04 02:03:03 +03:00
if (!rename($strPathOpSource, $strPathOpDestination))
{
my $strError = "${strPathOpSource} could not be moved:" . $!;
my $iErrorCode = COMMAND_ERR_FILE_MOVE;
unless (-e $strPathOpSource)
{
$strError = "${strPathOpSource} does not exist";
$iErrorCode = COMMAND_ERR_FILE_MISSING;
}
if ($strSourcePathType eq PATH_ABSOLUTE)
{
print $strError;
exit ($iErrorCode);
}
confess &log(ERROR, "${strErrorPrefix}: " . $strError);
}
2014-02-05 02:48:39 +03:00
}
}
2014-02-03 03:03:05 +03:00
####################################################################################################################################
# PIPE Function
#
# Copies data from one file handle to another, optionally compressing or decompressing the data in stream.
2014-02-03 03:03:05 +03:00
####################################################################################################################################
2014-06-06 05:42:47 +03:00
sub pipe
2014-02-03 03:03:05 +03:00
{
2014-02-06 05:39:08 +03:00
my $self = shift;
my $hIn = shift;
my $hOut = shift;
my $bCompress = shift;
my $bUncompress = shift;
# If compression is requested and the file is not already compressed
if (defined($bCompress) && $bCompress)
2014-02-05 19:35:09 +03:00
{
if (!gzip($hIn => $hOut))
{
confess $GzipError;
}
2014-02-05 19:35:09 +03:00
}
# If no compression is requested and the file is already compressed
elsif (defined($bUncompress) && $bUncompress)
2014-02-03 03:03:05 +03:00
{
if (!gunzip($hIn => $hOut))
{
confess $GunzipError;
}
2014-02-03 03:03:05 +03:00
}
# Else it's a straight copy
else
2014-02-03 03:03:05 +03:00
{
my $strBuffer;
my $iResultRead;
my $iResultWrite;
2014-06-06 05:42:47 +03:00
# Read from the input handle
while (($iResultRead = sysread($hIn, $strBuffer, BLOCK_SIZE)) != 0)
{
if (!defined($iResultRead))
{
confess $!;
last;
}
else
{
# Write to the output handle
$iResultWrite = syswrite($hOut, $strBuffer, $iResultRead);
2014-06-06 05:42:47 +03:00
if (!defined($iResultWrite) || $iResultWrite != $iResultRead)
{
confess $!;
last;
}
}
}
2014-02-03 03:03:05 +03:00
}
}
####################################################################################################################################
# COPY
#
# Copies a file from one location to another:
2014-06-06 05:42:47 +03:00
#
# * source and destination can be local or remote
# * wire and output compression/decompression are supported
# * intermediate temp files are used to prevent partial copies
# * modification time and permissions can be set on destination file
# * destination path can optionally be created
####################################################################################################################################
sub copy
{
my $self = shift;
my $strSourcePathType = shift;
my $strSourceFile = shift;
my $strDestinationPathType = shift;
my $strDestinationFile = shift;
my $bIgnoreMissingSource = shift;
my $bCompress = shift;
my $bPathCreate = shift;
my $lModificationTime = shift;
my $strPermission = shift;
2014-02-03 03:03:05 +03:00
# Set defaults
2014-06-06 05:42:47 +03:00
$bCompress = defined($bCompress) ? $bCompress : defined($self->{bCompress}) ? $self->{bCompress} : true;
$bIgnoreMissingSource = defined($bIgnoreMissingSource) ? $bIgnoreMissingSource : false;
$bPathCreate = defined($bPathCreate) ? $bPathCreate : false;
2014-06-04 18:58:30 +03:00
# Set working variables
my $strErrorPrefix = "File->copy";
my $bSourceRemote = $self->is_remote($strSourcePathType);
my $bDestinationRemote = $self->is_remote($strDestinationPathType);
my $strSourceOp = $self->path_get($strSourcePathType, $strSourceFile);
my $strDestinationOp = $self->path_get($strDestinationPathType, $strDestinationFile);
my $strDestinationTmpOp = $self->path_get($strDestinationPathType, $strDestinationFile, true);
my $strError;
2014-02-05 16:21:27 +03:00
2014-06-06 05:42:47 +03:00
# Determine if the file needs compression extension
if ($bCompress && $strDestinationOp !~ "^.*\.$self->{strCompressExtension}\$")
{
$strDestinationOp .= "." . $self->{strCompressExtension};
}
# Output trace info
&log(TRACE, "${strErrorPrefix}:" . ($bSourceRemote ? " remote" : " local") . " ${strSourcePathType}:${strSourceFile}" .
2014-06-06 05:42:47 +03:00
" to" . ($bDestinationRemote ? " remote" : " local") . " ${strDestinationPathType}:${strDestinationFile}" .
", compress = " . ($bCompress ? "true" : "false"));
2014-02-03 03:03:05 +03:00
# If source or destination are remote
if ($bSourceRemote || $bDestinationRemote)
2014-02-03 03:03:05 +03:00
{
# Get the ssh connection
my $oSSH;
2014-02-03 03:03:05 +03:00
# If source is local and destination is remote then use the destination connection
if (!$bSourceRemote && $bDestinationRemote)
{
$oSSH = $self->remote_get($strDestinationPathType);
}
# Else source connection is always used (because if both are remote they must be the same remote)
else
{
$oSSH = $self->remote_get($strSourcePathType);
}
2014-06-04 18:58:30 +03:00
# Build the command and open the local file
my $hFile;
my $strCommand;
2014-06-04 18:58:30 +03:00
# If source is remote and destination is local
if ($bSourceRemote && !$bDestinationRemote)
{
# Build the command string
$strCommand = $self->{strCommand} .
" --compress copy_in ${strSourceOp}";
2014-02-03 03:03:05 +03:00
open($hFile, ">", $strDestinationTmpOp)
or confess &log(ERROR, "cannot open ${strDestinationTmpOp}: " . $!);
2014-02-03 03:03:05 +03:00
}
# Else if source is local and destination is remote
elsif (!$bSourceRemote && $bDestinationRemote)
2014-02-03 03:03:05 +03:00
{
# Build the command string
$strCommand = $self->{strCommand} .
2014-06-06 05:42:47 +03:00
($bCompress ? " --compress" : " --uncompress") .
" copy_out ${strDestinationOp}";
# Open source file for reading
open($hFile, "<", $strSourceOp)
or confess &log(ERROR, "cannot open ${strSourceOp}: " . $!);
}
# Else source and destination are remote
else
{
if ($self->path_type_get($strSourcePathType) ne $self->path_type_get($strDestinationPathType))
2014-05-27 16:00:24 +03:00
{
confess &log(ASSERT, "remote source and destination not supported");
2014-05-27 16:00:24 +03:00
}
# !!! MULTIPLE REMOTE COPY NOT YET IMPLEMENTED
return false;
2014-02-03 03:03:05 +03:00
}
2014-06-06 05:42:47 +03:00
&log(TRACE, "${strErrorPrefix} command:" . $strCommand);
# Execute the ssh command
my ($hIn, $hOut, $hErr, $pId) = $oSSH->open3($strCommand)
or confess &log("unable to execute ssh '${strCommand}': " . $self->error_get());
# If source is remote and destination is local
if ($bSourceRemote && !$bDestinationRemote)
{
$self->pipe($hOut, $hFile, $bCompress, !$bCompress);
2014-02-03 03:03:05 +03:00
}
# Else if source is local and destination is remote
elsif (!$bSourceRemote && $bDestinationRemote)
2014-02-03 03:03:05 +03:00
{
$self->pipe($hFile, $hIn, $bCompress, !$bCompress);
}
2014-06-04 18:58:30 +03:00
2014-06-06 05:42:47 +03:00
# Close hIn
close($hIn);
# Read STDERR into a string
2014-06-06 05:42:47 +03:00
my $strError;
my $hErrString = IO::String->new($strError);
$self->pipe($hErr, $hErrString);
# Wait for the process to finish and report any errors
waitpid($pId, 0);
my $iExitStatus = ${^CHILD_ERROR_NATIVE} >> 8;
2014-06-06 05:42:47 +03:00
# close($hErrString);
if ($iExitStatus != 0)
{
2014-06-06 05:42:47 +03:00
confess &log(ERROR, "command '${strCommand}' returned " . $iExitStatus . ": " .
(defined($strError) ? $strError : "[unknown]"));
}
2014-05-27 16:00:24 +03:00
# Close the destination file handle
if (defined($hFile))
{
close($hFile) or confess &log(ERROR, "cannot close file ${strDestinationTmpOp}");
2014-02-03 03:03:05 +03:00
}
}
else
2014-02-05 19:35:09 +03:00
{
# !!! LOCAL COPY NOT YET IMPLEMENTED
return false;
2014-02-05 19:35:09 +03:00
}
2014-02-07 00:37:37 +03:00
if (!$bDestinationRemote)
2014-02-05 19:35:09 +03:00
{
# Set the file permission if required
if (defined($strPermission))
{
system("chmod ${strPermission} ${strDestinationTmpOp}") == 0
or confess &log(ERROR, "unable to set permissions for local ${strDestinationTmpOp}");
}
2014-06-06 05:42:47 +03:00
# Set the file modification time if required (this only works locally for now)
if (defined($lModificationTime))
{
utime($lModificationTime, $lModificationTime, $strDestinationTmpOp)
or confess &log(ERROR, "unable to set time for local ${strDestinationTmpOp}");
}
2014-06-06 05:42:47 +03:00
# Move the file from tmp to final destination
$self->move($self->path_type_get($strDestinationPathType) . ":absolute", $strDestinationTmpOp,
$self->path_type_get($strDestinationPathType) . ":absolute", $strDestinationOp, $bPathCreate);
2014-02-05 19:35:09 +03:00
}
2014-02-07 00:37:37 +03:00
return true;
2014-02-03 03:03:05 +03:00
}
####################################################################################################################################
2014-06-02 00:23:33 +03:00
# HASH
2014-02-03 03:03:05 +03:00
####################################################################################################################################
2014-06-02 00:23:33 +03:00
sub hash
2014-02-03 03:03:05 +03:00
{
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-06-02 00:23:33 +03:00
my $strHashType = 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-06-02 00:23:33 +03:00
my $strHash;
my $strErrorPrefix = "File->hash";
my $bRemote = $self->is_remote($strPathType);
my $strPath = $self->path_get($strPathType, $strFile);
&log(TRACE, "${strErrorPrefix}: " . ($bRemote ? "remote" : "local") . " ${strPathType}:${strPath}");
2014-02-07 00:37:37 +03:00
2014-06-02 00:23:33 +03:00
if ($bRemote)
2014-02-03 03:03:05 +03:00
{
2014-06-02 00:23:33 +03:00
# Run remotely
my $oSSH = $self->remote_get($strPathType);
my $strOutput = $oSSH->capture($self->{strCommand} . " hash ${strPath}");
2014-02-07 00:37:37 +03:00
2014-06-02 00:23:33 +03:00
# Capture any errors
if ($oSSH->error)
{
confess &log(ERROR, "${strErrorPrefix} remote: " . (defined($strOutput) ? $strOutput : $oSSH->error));
}
2014-02-07 00:37:37 +03:00
2014-06-02 00:23:33 +03:00
$strHash = $strOutput;
2014-02-03 03:03:05 +03:00
}
else
{
2014-06-02 00:23:33 +03:00
my $hFile;
if (!open($hFile, "<", $strPath))
{
my $strError = "${strPath} could not be read" . $!;
my $iErrorCode = 2;
unless (-e $strPath)
{
$strError = "${strPath} does not exist";
$iErrorCode = 1;
}
if ($strPathType eq PATH_ABSOLUTE)
{
print $strError;
exit ($iErrorCode);
}
confess &log(ERROR, "${strErrorPrefix}: " . $strError);
}
my $oSHA = Digest::SHA->new(defined($strHashType) ? $strHashType : 'sha1');
$oSHA->addfile($hFile);
close($hFile);
$strHash = $oSHA->hexdigest();
2014-02-03 03:03:05 +03:00
}
2014-02-07 00:37:37 +03:00
2014-06-02 00:23:33 +03:00
return $strHash;
2014-02-03 03:03:05 +03:00
}
2014-06-02 00:23:33 +03:00
####################################################################################################################################
2014-06-03 00:48:07 +03:00
# COMPRESS
####################################################################################################################################
2014-06-03 00:48:07 +03:00
sub compress
{
my $self = shift;
my $strPathType = shift;
my $strFile = shift;
2014-06-03 00:48:07 +03:00
# Get the root path for the file list
my $strErrorPrefix = "File->compress";
my $bRemote = $self->is_remote($strPathType);
my $strPathOp = $self->path_get($strPathType, $strFile);
&log(TRACE, "${strErrorPrefix}: " . ($bRemote ? "remote" : "local") . " ${strPathType}:${strPathOp}");
# Run remotely
2014-06-04 02:03:03 +03:00
if ($bRemote)
{
2014-06-03 00:48:07 +03:00
my $strCommand = $self->{strCommand} .
" compress ${strPathOp}";
2014-06-03 00:48:07 +03:00
# Run via SSH
my $oSSH = $self->remote_get($strPathType);
my $strOutput = $oSSH->capture($strCommand);
# Handle any errors
if ($oSSH->error)
{
confess &log(ERROR, "${strErrorPrefix} remote (${strCommand}): " . (defined($strOutput) ? $strOutput : $oSSH->error));
}
}
2014-06-03 00:48:07 +03:00
# Run locally
else
{
if (!gzip($strPathOp => "${strPathOp}.gz"))
{
my $strError = "${strPathOp} could not be compressed:" . $!;
my $iErrorCode = 2;
2014-06-03 00:48:07 +03:00
unless (-e $strPathOp)
{
$strError = "${strPathOp} does not exist";
$iErrorCode = 1;
}
2014-06-03 00:48:07 +03:00
if ($strPathType eq PATH_ABSOLUTE)
{
print $strError;
exit ($iErrorCode);
}
2014-06-03 00:48:07 +03:00
confess &log(ERROR, "${strErrorPrefix}: " . $strError);
}
}
}
2014-02-03 03:03:05 +03:00
####################################################################################################################################
2014-06-02 00:23:33 +03:00
# LIST
2014-02-03 03:03:05 +03:00
####################################################################################################################################
2014-06-02 00:23:33 +03:00
sub list
2014-02-03 03:03:05 +03:00
{
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
# Get the root path for the file list
2014-06-02 00:23:33 +03:00
my @stryFileList;
my $strErrorPrefix = "File->list";
my $bRemote = $self->is_remote($strPathType);
my $strPathOp = $self->path_get($strPathType, $strPath);
2014-02-07 00:37:37 +03:00
2014-06-04 18:58:30 +03:00
&log(TRACE, "${strErrorPrefix}: " . ($bRemote ? "remote" : "local") . " ${strPathType}:${strPathOp}" .
2014-06-02 00:23:33 +03:00
", expression " . (defined($strExpression) ? $strExpression : "[UNDEF]") .
", sort " . (defined($strSortOrder) ? $strSortOrder : "[UNDEF]"));
2014-02-03 03:03:05 +03:00
2014-03-26 01:56:05 +03:00
# Run remotely
if ($self->is_remote($strPathType))
2014-02-03 03:03:05 +03:00
{
2014-06-02 00:23:33 +03:00
my $strCommand = $self->{strCommand} .
(defined($strSortOrder) && $strSortOrder eq "reverse" ? " --sort=reverse" : "") .
(defined($strExpression) ? " --expression=\"" . $strExpression . "\"" : "") .
" list ${strPathOp}";
2014-02-07 00:37:37 +03:00
2014-06-02 00:23:33 +03:00
# Run via SSH
2014-03-26 01:56:05 +03:00
my $oSSH = $self->remote_get($strPathType);
2014-06-02 00:23:33 +03:00
my $strOutput = $oSSH->capture($strCommand);
# Handle any errors
2014-04-28 16:13:25 +03:00
if ($oSSH->error)
{
2014-06-02 00:23:33 +03:00
confess &log(ERROR, "${strErrorPrefix} remote (${strCommand}): " . (defined($strOutput) ? $strOutput : $oSSH->error));
2014-04-28 16:13:25 +03:00
}
2014-06-02 00:23:33 +03:00
@stryFileList = split(/\n/, $strOutput);
2014-03-26 01:56:05 +03:00
}
# Run locally
else
2014-02-03 03:03:05 +03:00
{
2014-06-02 00:23:33 +03:00
my $hPath;
2014-02-07 00:37:37 +03:00
2014-06-02 00:23:33 +03:00
if (!opendir($hPath, $strPathOp))
{
my $strError = "${strPathOp} could not be read:" . $!;
my $iErrorCode = 2;
2014-03-26 01:56:05 +03:00
2014-06-02 00:23:33 +03:00
unless (-e $strPath)
{
$strError = "${strPathOp} does not exist";
$iErrorCode = 1;
}
if ($strPathType eq PATH_ABSOLUTE)
{
print $strError;
exit ($iErrorCode);
}
confess &log(ERROR, "${strErrorPrefix}: " . $strError);
}
@stryFileList = grep(!/^(\.)|(\.\.)$/i, readdir($hPath));
close($hPath);
if (defined($strExpression))
{
@stryFileList = grep(/$strExpression/i, @stryFileList);
}
# Reverse sort
if (defined($strSortOrder) && $strSortOrder eq "reverse")
{
@stryFileList = sort {$b cmp $a} @stryFileList;
}
# Normal sort
else
{
@stryFileList = sort @stryFileList;
}
}
2014-06-02 00:23:33 +03:00
# Return file list
return @stryFileList;
2014-02-03 03:03:05 +03:00
}
####################################################################################################################################
2014-06-02 00:23:33 +03:00
# EXISTS - Checks for the existence of a file, but does not imply that the file is readable/writeable.
#
# Return: true if file exists, false otherwise
####################################################################################################################################
2014-06-02 00:23:33 +03:00
sub exists
{
my $self = shift;
my $strPathType = shift;
my $strPath = shift;
2014-06-02 00:23:33 +03:00
# Set error prefix, remote, and path
my $bExists = false;
my $strErrorPrefix = "File->exists";
my $bRemote = $self->is_remote($strPathType);
my $strPathOp = $self->path_get($strPathType, $strPath);
2014-06-02 00:23:33 +03:00
&log(TRACE, "${strErrorPrefix}: " . ($bRemote ? "remote" : "local") . " ${strPathType}:${strPathOp}");
# Run remotely
2014-06-02 00:23:33 +03:00
if ($bRemote)
{
2014-06-02 00:23:33 +03:00
# Build the command
my $strCommand = $self->{strCommand} . " exists ${strPathOp}";
2014-06-04 18:58:30 +03:00
2014-06-02 00:23:33 +03:00
# Run it remotely
my $oSSH = $self->remote_get($strPathType);
2014-06-02 00:23:33 +03:00
my $strOutput = $oSSH->capture($strCommand);
2014-05-27 16:00:24 +03:00
2014-06-02 00:23:33 +03:00
# Capture any errors
2014-05-13 18:23:15 +03:00
if ($oSSH->error)
{
2014-06-02 00:23:33 +03:00
confess &log(ERROR, "${strErrorPrefix} remote (${strCommand}): " . (defined($strOutput) ? $strOutput : $oSSH->error));
2014-05-13 18:23:15 +03:00
}
2014-05-27 16:00:24 +03:00
2014-06-02 00:23:33 +03:00
$bExists = $strOutput eq "Y";
}
# Run locally
else
{
2014-06-02 00:23:33 +03:00
if (-e $strPathOp)
2014-05-27 16:00:24 +03:00
{
2014-06-02 00:23:33 +03:00
$bExists = true;
2014-05-27 16:00:24 +03:00
}
}
2014-06-02 00:23:33 +03:00
return $bExists;
}
####################################################################################################################################
2014-06-02 00:23:33 +03:00
# REMOVE
####################################################################################################################################
2014-06-02 00:23:33 +03:00
sub remove
{
my $self = shift;
my $strPathType = shift;
my $strPath = shift;
my $bTemp = shift;
2014-06-02 00:23:33 +03:00
my $bIgnoreMissing = shift;
2014-06-04 18:58:30 +03:00
2014-06-02 00:23:33 +03:00
if (!defined($bIgnoreMissing))
{
2014-06-02 00:23:33 +03:00
$bIgnoreMissing = true;
}
# Get the root path for the manifest
2014-06-02 00:23:33 +03:00
my $bRemoved = true;
my $strErrorPrefix = "File->remove";
my $bRemote = $self->is_remote($strPathType);
my $strPathOp = $self->path_get($strPathType, $strPath, $bTemp);
2014-06-02 00:23:33 +03:00
&log(TRACE, "${strErrorPrefix}: " . ($bRemote ? "remote" : "local") . " ${strPathType}:${strPathOp}");
# Run remotely
2014-06-02 00:23:33 +03:00
if ($bRemote)
{
2014-06-02 00:23:33 +03:00
# Build the command
my $strCommand = $self->{strCommand} . ($bIgnoreMissing ? " --ignore-missing" : "") . " remove ${strPathOp}";
2014-06-04 18:58:30 +03:00
2014-06-02 00:23:33 +03:00
# Run it remotely
my $oSSH = $self->remote_get($strPathType);
my $strOutput = $oSSH->capture($strCommand);
2014-05-13 18:23:15 +03:00
if ($oSSH->error)
{
2014-06-02 00:23:33 +03:00
confess &log(ERROR, "${strErrorPrefix} remote (${strCommand}): " . (defined($strOutput) ? $strOutput : $oSSH->error));
2014-05-13 18:23:15 +03:00
}
2014-06-02 00:23:33 +03:00
$bRemoved = $strOutput eq "Y";
}
# Run locally
else
{
2014-06-02 00:23:33 +03:00
if (unlink($strPathOp) != 1)
{
$bRemoved = false;
if (-e $strPathOp || !$bIgnoreMissing)
{
my $strError = "${strPathOp} could not be removed: " . $!;
my $iErrorCode = 2;
unless (-e $strPathOp)
{
$strError = "${strPathOp} does not exist";
$iErrorCode = 1;
}
if ($strPathType eq PATH_ABSOLUTE)
{
print $strError;
exit ($iErrorCode);
}
confess &log(ERROR, "${strErrorPrefix}: " . $strError);
}
}
}
2014-06-04 05:02:56 +03:00
2014-06-02 00:23:33 +03:00
return $bRemoved;
}
2014-02-03 03:03:05 +03:00
####################################################################################################################################
2014-06-02 01:39:35 +03:00
# MANIFEST
2014-02-03 03:03:05 +03:00
#
# 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.
####################################################################################################################################
2014-06-02 01:39:35 +03:00
sub manifest
2014-02-03 03:03:05 +03:00
{
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-06-02 01:39:35 +03:00
my $oManifestHashRef = shift;
2014-02-03 03:03:05 +03:00
# Get the root path for the manifest
2014-06-02 01:39:35 +03:00
my $strErrorPrefix = "File->manifest";
my $bRemote = $self->is_remote($strPathType);
my $strPathOp = $self->path_get($strPathType, $strPath);
2014-02-03 03:03:05 +03:00
2014-06-02 01:39:35 +03:00
&log(TRACE, "${strErrorPrefix}: " . ($bRemote ? "remote" : "local") . " ${strPathType}:${strPathOp}");
2014-02-03 03:03:05 +03:00
# Run remotely
2014-06-02 01:39:35 +03:00
if ($bRemote)
2014-02-03 03:03:05 +03:00
{
2014-06-02 01:39:35 +03:00
# Build the command
my $strCommand = $self->{strCommand} . " manifest ${strPathOp}";
2014-06-04 18:58:30 +03:00
2014-06-02 01:39:35 +03:00
# Run it remotely
2014-02-06 05:39:08 +03:00
my $oSSH = $self->remote_get($strPathType);
2014-06-02 01:39:35 +03:00
my $strOutput = $oSSH->capture($strCommand);
if ($oSSH->error)
{
confess &log(ERROR, "${strErrorPrefix} remote (${strCommand}): " . (defined($strOutput) ? $strOutput : $oSSH->error));
}
2014-06-04 05:02:56 +03:00
return data_hash_build($oManifestHashRef, $strOutput, "\t", ".");
2014-02-03 03:03:05 +03:00
}
# Run locally
else
{
manifest_recurse($strPathType, $strPathOp, undef, 0, $oManifestHashRef);
2014-02-03 03:03:05 +03:00
}
2014-06-02 01:39:35 +03:00
}
sub manifest_recurse
{
my $strPathType = shift;
my $strPathOp = shift;
my $strPathFileOp = shift;
my $iDepth = shift;
2014-06-02 01:39:35 +03:00
my $oManifestHashRef = shift;
2014-02-03 03:03:05 +03:00
my $strErrorPrefix = "File->manifest";
my $strPathRead = $strPathOp . (defined($strPathFileOp) ? "/${strPathFileOp}" : "");
my $hPath;
if (!opendir($hPath, $strPathRead))
{
my $strError = "${strPathRead} could not be read:" . $!;
my $iErrorCode = 2;
unless (-e $strPathRead)
{
$strError = "${strPathRead} does not exist";
$iErrorCode = 1;
}
if ($strPathType eq PATH_ABSOLUTE)
{
print $strError;
exit ($iErrorCode);
}
confess &log(ERROR, "${strErrorPrefix}: " . $strError);
}
my @stryFileList = grep(!/^\..$/i, readdir($hPath));
close($hPath);
foreach my $strFile (@stryFileList)
{
my $strPathFile = "${strPathRead}/$strFile";
my $bCurrentDir = $strFile eq ".";
if ($iDepth != 0)
{
if ($bCurrentDir)
{
$strFile = $strPathFileOp;
$strPathFile = $strPathRead;
}
else
{
$strFile = "${strPathFileOp}/${strFile}";
}
}
my $oStat = lstat($strPathFile);
if (!defined($oStat))
{
if (-e $strPathFile)
{
my $strError = "${strPathFile} could not be read: " . $!;
if ($strPathType eq PATH_ABSOLUTE)
{
print $strError;
exit COMMAND_ERR_FILE_READ;
}
confess &log(ERROR, "${strErrorPrefix}: " . $strError);
}
2014-06-04 05:02:56 +03:00
next;
}
# Check for regular file
if (S_ISREG($oStat->mode))
{
${$oManifestHashRef}{name}{"${strFile}"}{type} = "f";
# Get inode
${$oManifestHashRef}{name}{"${strFile}"}{inode} = $oStat->ino;
# Get size
${$oManifestHashRef}{name}{"${strFile}"}{size} = $oStat->size;
# Get modification time
${$oManifestHashRef}{name}{"${strFile}"}{modification_time} = $oStat->mtime;
}
# Check for directory
elsif (S_ISDIR($oStat->mode))
{
${$oManifestHashRef}{name}{"${strFile}"}{type} = "d";
}
# Check for link
elsif (S_ISLNK($oStat->mode))
{
${$oManifestHashRef}{name}{"${strFile}"}{type} = "l";
# Get link destination
${$oManifestHashRef}{name}{"${strFile}"}{link_destination} = readlink($strPathFile);
2014-06-04 18:58:30 +03:00
if (!defined(${$oManifestHashRef}{name}{"${strFile}"}{link_destination}))
{
if (-e $strPathFile)
{
my $strError = "${strPathFile} error reading link: " . $!;
if ($strPathType eq PATH_ABSOLUTE)
{
print $strError;
exit COMMAND_ERR_LINK_READ;
}
confess &log(ERROR, "${strErrorPrefix}: " . $strError);
}
}
}
else
{
my $strError = "${strPathFile} is not of type directory, file, or link";
if ($strPathType eq PATH_ABSOLUTE)
{
print $strError;
exit COMMAND_ERR_FILE_TYPE;
}
confess &log(ERROR, "${strErrorPrefix}: " . $strError);
}
# Get user name
${$oManifestHashRef}{name}{"${strFile}"}{user} = getpwuid($oStat->uid);
# Get group name
${$oManifestHashRef}{name}{"${strFile}"}{group} = getgrgid($oStat->gid);
# Get permissions
if (${$oManifestHashRef}{name}{"${strFile}"}{type} ne "l")
{
${$oManifestHashRef}{name}{"${strFile}"}{permission} = sprintf("%04o", S_IMODE($oStat->mode));
}
# Recurse into directories
if (${$oManifestHashRef}{name}{"${strFile}"}{type} eq "d" && !$bCurrentDir)
{
manifest_recurse($strPathType, $strPathOp,
2014-06-04 18:58:30 +03:00
$strFile,
$iDepth + 1, $oManifestHashRef);
}
}
2014-02-03 03:03:05 +03:00
}
2014-02-06 05:39:08 +03:00
no Moose;
2014-06-02 00:23:33 +03:00
__PACKAGE__->meta->make_immutable;