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

1637 lines
54 KiB
Perl
Raw Normal View History

2014-02-03 03:03:05 +03:00
####################################################################################################################################
# FILE MODULE
####################################################################################################################################
package BackRest::File;
2014-02-03 03:03:05 +03:00
use threads;
2014-02-03 03:03:05 +03:00
use strict;
use warnings;
use Carp;
use POSIX qw(ceil);
2014-02-03 03:03:05 +03:00
use Net::OpenSSH;
use File::Basename qw(dirname basename);
use File::Copy qw(cp);
use File::Path qw(make_path remove_tree);
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;
use Time::HiRes qw(gettimeofday usleep);
2014-02-03 03:03:05 +03:00
use lib dirname($0) . '/../lib';
2014-06-07 18:51:27 +03:00
use BackRest::Exception;
2014-06-07 23:13:41 +03:00
use BackRest::Utility;
use BackRest::Remote;
use BackRest::ProcessAsync;
2014-02-03 03:03:05 +03:00
2014-06-22 03:08:49 +03:00
# Exports
2014-02-03 03:03:05 +03:00
use Exporter qw(import);
our @EXPORT = qw(PATH_ABSOLUTE PATH_DB PATH_DB_ABSOLUTE PATH_BACKUP PATH_BACKUP_ABSOLUTE
2014-06-22 03:08:49 +03:00
PATH_BACKUP_CLUSTER PATH_BACKUP_TMP PATH_BACKUP_ARCHIVE
2014-06-06 05:42:47 +03:00
COMMAND_ERR_FILE_MISSING COMMAND_ERR_FILE_READ COMMAND_ERR_FILE_MOVE COMMAND_ERR_FILE_TYPE
2014-06-06 06:51:27 +03:00
COMMAND_ERR_LINK_READ COMMAND_ERR_PATH_MISSING COMMAND_ERR_PATH_CREATE COMMAND_ERR_PARAM
2014-06-07 23:06:46 +03:00
PIPE_STDIN PIPE_STDOUT PIPE_STDERR
2014-06-22 17:54:31 +03:00
2015-01-07 19:58:21 +02:00
OP_FILE_OWNER OP_FILE_WAIT OP_FILE_LIST OP_FILE_EXISTS OP_FILE_HASH OP_FILE_REMOVE OP_FILE_MANIFEST
OP_FILE_COMPRESS OP_FILE_MOVE OP_FILE_COPY OP_FILE_COPY_OUT OP_FILE_COPY_IN OP_FILE_PATH_CREATE);
2014-06-07 23:06:46 +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,
2014-06-22 01:19:37 +03:00
COMMAND_ERR_PATH_READ => 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',
2014-06-07 04:16:24 +03:00
PATH_BACKUP_ARCHIVE => 'backup:archive'
2014-04-28 16:13:25 +03:00
};
2014-06-06 06:51:27 +03:00
####################################################################################################################################
# STD Pipe Constants
####################################################################################################################################
use constant
{
PIPE_STDIN => '<STDIN>',
PIPE_STDOUT => '<STDOUT>',
PIPE_STDERR => '<STDERR>'
2014-06-06 06:51:27 +03:00
};
2014-06-07 23:06:46 +03:00
####################################################################################################################################
# Operation constants
####################################################################################################################################
use constant
{
2015-01-07 19:58:21 +02:00
OP_FILE_OWNER => 'File->owner',
OP_FILE_WAIT => 'File->wait',
OP_FILE_LIST => 'File->list',
OP_FILE_EXISTS => 'File->exists',
OP_FILE_HASH => 'File->hash',
OP_FILE_REMOVE => 'File->remove',
OP_FILE_MANIFEST => 'File->manifest',
OP_FILE_COMPRESS => 'File->compress',
OP_FILE_MOVE => 'File->move',
OP_FILE_COPY => 'File->copy',
OP_FILE_COPY_OUT => 'File->copy_out',
OP_FILE_COPY_IN => 'File->copy_in',
OP_FILE_PATH_CREATE => 'File->path_create',
OP_FILE_LINK_CREATE => 'File->link_create'
2014-06-07 23:06:46 +03:00
};
2014-02-07 00:37:37 +03:00
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
2014-10-10 22:13:28 +03:00
sub new
2014-02-03 03:03:05 +03:00
{
2014-10-10 22:13:28 +03:00
my $class = shift;
my $strStanza = shift;
my $strBackupPath = shift;
my $strRemote = shift;
my $oRemote = shift;
my $strDefaultPathPermission = shift;
my $strDefaultFilePermission = shift;
my $iThreadIdx = shift;
# Create the class hash
my $self = {};
bless $self, $class;
# Default compression extension to gz
$self->{strCompressExtension} = 'gz';
# Default file and path permissions
$self->{strDefaultPathPermission} = defined($strDefaultPathPermission) ? $strDefaultPathPermission : '0750';
$self->{strDefaultFilePermission} = defined($strDefaultFilePermission) ? $strDefaultFilePermission : '0640';
# Initialize other variables
$self->{strStanza} = $strStanza;
$self->{strBackupPath} = $strBackupPath;
$self->{strRemote} = $strRemote;
$self->{oRemote} = $oRemote;
$self->{iThreadIdx} = $iThreadIdx;
2014-06-04 18:58:30 +03:00
2014-06-07 04:16:24 +03:00
# If remote is defined check parameters and open session
if (defined($self->{strRemote}) && $self->{strRemote} ne NONE)
2014-02-03 22:50:23 +03:00
{
2014-06-07 04:16:24 +03:00
# Make sure remote is valid
if ($self->{strRemote} ne DB && $self->{strRemote} ne BACKUP)
{
confess &log(ASSERT, 'strRemote must be "' . DB . '" or "' . BACKUP .
2014-10-10 22:13:28 +03:00
"\", $self->{strRemote} was passed");
}
2014-06-04 18:58:30 +03:00
2014-06-07 04:16:24 +03:00
# Remote object must be set
if (!defined($self->{oRemote}))
2014-02-17 03:01:06 +03:00
{
confess &log(ASSERT, 'oRemote must be defined');
2014-02-17 03:01:06 +03:00
}
2014-02-03 22:50:23 +03:00
}
2014-10-10 22:13:28 +03:00
return $self;
2014-02-03 03:03:05 +03:00
}
####################################################################################################################################
# DESTRUCTOR
####################################################################################################################################
sub DEMOLISH
{
my $self = shift;
if (defined($self->{oRemote}))
{
$self->{oRemote} = undef;
}
if (defined($self->{oProcessAsync}))
{
$self->{oProcessAsync} = undef;
}
}
2014-02-11 23:31:16 +03:00
####################################################################################################################################
# CLONE
####################################################################################################################################
sub clone
{
my $self = shift;
my $iThreadIdx = shift;
2014-06-22 03:08:49 +03:00
return BackRest::File->new
2014-02-11 23:31:16 +03:00
(
2014-10-10 22:13:28 +03:00
$self->{strStanza},
$self->{strBackupPath},
$self->{strRemote},
defined($self->{oRemote}) ? $self->{oRemote}->clone() : undef,
$self->{strDefaultPathPermission},
$self->{strDefaultFilePermission},
$iThreadIdx
2014-02-11 23:31:16 +03:00
);
}
####################################################################################################################################
# PROCESS_ASYNC_GET
####################################################################################################################################
sub process_async_get
{
my $self = shift;
if (!defined($self->{oProcessAsync}))
{
$self->{oProcessAsync} = new BackRest::ProcessAsync;
}
return $self->{oProcessAsync};
}
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
{
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
{
confess &log(ASSERT, 'strBackupPath not 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)
{
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 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 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
{
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-13 23:26:07 +03:00
}
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
return $strArchivePath . (defined($strArchive) ? '/' . substr($strArchive, 0, 16) : '') .
(defined($strFile) ? '/' . $strFile : '');
2014-02-05 23:56:05 +03:00
}
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
#
2014-06-07 04:16:24 +03:00
# Determine whether the path type is remote
####################################################################################################################################
sub is_remote
{
my $self = shift;
my $strPathType = shift;
2014-06-07 04:16:24 +03:00
return defined($self->{strRemote}) && $self->path_type_get($strPathType) eq $self->{strRemote};
}
2014-02-03 03:03:05 +03:00
####################################################################################################################################
2014-09-19 00:24:40 +03:00
# LINK_CREATE
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 (e.g. both PATH_DB or both PATH_BACKUP, etc.)
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-04 03:03:17 +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
# Set operation and debug strings
my $strOperation = OP_FILE_LINK_CREATE;
my $strDebug = "${strSourcePathType}" . (defined($strSource) ? ":${strSource}" : '') .
" to ${strDestinationPathType}" . (defined($strDestination) ? ":${strDestination}" : '') .
', hard = ' . ($bHard ? 'true' : 'false') . ", relative = " . ($bRelative ? 'true' : 'false') .
', destination_path_create = ' . ($bPathCreate ? 'true' : 'false');
&log(DEBUG, "${strOperation}: ${strDebug}");
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
}
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
{
confess &log(ASSERT, "${strDebug}: remote operation not supported");
2014-02-04 03:03:17 +03:00
}
# Run locally
else
{
if ($bHard)
{
link($strSource, $strDestination)
or confess &log(ERROR, "unable to create hardlink from ${strSource} to ${strDestination}");
}
else
{
symlink($strSource, $strDestination)
or confess &log(ERROR, "unable to create symlink from ${strSource} to ${strDestination}");
}
2014-02-04 03:03:17 +03:00
}
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-21 21:19:03 +03:00
# Set defaults
$bDestinationPathCreate = defined($bDestinationPathCreate) ? $bDestinationPathCreate : false;
2014-02-05 21:10:36 +03:00
2014-06-21 21:19:03 +03:00
# Set operation variables
my $strPathOpSource = $self->path_get($strSourcePathType, $strSourceFile);
my $strPathOpDestination = $self->path_get($strDestinationPathType, $strDestinationFile);
# Set operation and debug strings
my $strOperation = OP_FILE_MOVE;
my $strDebug = "${strSourcePathType}" . (defined($strSourceFile) ? ":${strSourceFile}" : '') .
" to ${strDestinationPathType}" . (defined($strDestinationFile) ? ":${strDestinationFile}" : '') .
', destination_path_create = ' . ($bDestinationPathCreate ? 'true' : 'false');
2014-06-21 21:19:03 +03:00
&log(DEBUG, "${strOperation}: ${strDebug}");
# Source and destination path types must be the same
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
{
2014-06-21 21:19:03 +03:00
confess &log(ASSERT, "${strDebug}: source and destination path types must be equal");
2014-02-05 21:10:36 +03:00
}
2014-02-05 02:48:39 +03:00
# Run remotely
2014-06-21 21:19:03 +03:00
if ($self->is_remote($strSourcePathType))
2014-02-05 02:48:39 +03:00
{
2014-06-22 01:19:37 +03:00
confess &log(ASSERT, "${strDebug}: remote operation not supported");
2014-02-05 02:48:39 +03:00
}
# Run locally
else
{
2014-06-24 01:54:00 +03:00
if (!rename($strPathOpSource, $strPathOpDestination))
2014-06-04 02:03:03 +03:00
{
2014-06-24 01:54:00 +03:00
my $strError = "${strPathOpDestination} could not be moved: " . $!;
my $iErrorCode = COMMAND_ERR_FILE_READ;
if (!$self->exists(PATH_ABSOLUTE, dirname($strPathOpDestination)))
2014-06-04 02:03:03 +03:00
{
2014-06-24 01:54:00 +03:00
$strError = "${strPathOpDestination} does not exist";
$iErrorCode = COMMAND_ERR_FILE_MISSING;
2014-06-04 02:03:03 +03:00
}
2014-06-24 01:54:00 +03:00
if (!($bDestinationPathCreate && $iErrorCode == COMMAND_ERR_FILE_MISSING))
{
2014-06-04 02:03:03 +03:00
if ($strSourcePathType eq PATH_ABSOLUTE)
{
2014-06-24 01:54:00 +03:00
confess &log(ERROR, $strError, $iErrorCode);
2014-06-04 02:03:03 +03:00
}
2014-02-05 02:48:39 +03:00
2014-06-21 21:19:03 +03:00
confess &log(ERROR, "${strDebug}: " . $strError);
2014-06-04 02:03:03 +03:00
}
2014-06-24 01:54:00 +03:00
$self->path_create(PATH_ABSOLUTE, dirname($strPathOpDestination));
2014-06-04 02:03:03 +03:00
2014-06-24 01:54:00 +03:00
if (!rename($strPathOpSource, $strPathOpDestination))
2014-06-04 02:03:03 +03:00
{
2014-09-19 00:39:24 +03:00
confess &log(ERROR, "unable to move file ${strPathOpSource}: " . $!);
2014-06-04 02:03:03 +03:00
}
}
2014-02-05 02:48:39 +03:00
}
}
2014-06-21 22:17:09 +03:00
####################################################################################################################################
# COMPRESS
####################################################################################################################################
sub compress
{
my $self = shift;
my $strPathType = shift;
my $strFile = shift;
# Set operation variables
my $strPathOp = $self->path_get($strPathType, $strFile);
# Set operation and debug strings
my $strOperation = OP_FILE_COMPRESS;
my $strDebug = "${strPathType}:${strPathOp}";
&log(DEBUG, "${strOperation}: ${strDebug}");
# Run remotely
if ($self->is_remote($strPathType))
{
2014-06-22 01:19:37 +03:00
confess &log(ASSERT, "${strDebug}: remote operation not supported");
2014-06-21 22:17:09 +03:00
}
# Run locally
else
{
# Compress the file
2014-06-21 22:17:09 +03:00
if (!gzip($strPathOp => "${strPathOp}.gz"))
{
my $strError = "${strPathOp} could not be compressed:" . $!;
2014-06-22 01:19:37 +03:00
my $iErrorCode = COMMAND_ERR_FILE_READ;
2014-06-21 22:17:09 +03:00
2014-06-22 01:19:37 +03:00
if (!$self->exists($strPathType, $strFile))
2014-06-21 22:17:09 +03:00
{
$strError = "${strPathOp} does not exist";
2014-06-22 01:19:37 +03:00
$iErrorCode = COMMAND_ERR_FILE_MISSING;
2014-06-21 22:17:09 +03:00
}
if ($strPathType eq PATH_ABSOLUTE)
{
2014-06-22 01:19:37 +03:00
confess &log(ERROR, $strError, $iErrorCode);
2014-06-21 22:17:09 +03:00
}
confess &log(ERROR, "${strDebug}: " . $strError);
}
2014-06-22 03:08:49 +03:00
# Remove the old file
2014-06-21 22:17:09 +03:00
unlink($strPathOp)
or die &log(ERROR, "${strDebug}: unable to remove ${strPathOp}");
}
}
####################################################################################################################################
# PATH_CREATE
#
# Creates a path locally or remotely.
####################################################################################################################################
sub path_create
{
my $self = shift;
my $strPathType = shift;
my $strPath = shift;
my $strMode = shift;
2014-06-22 21:51:28 +03:00
my $bIgnoreExists = shift;
2014-06-21 22:17:09 +03:00
# Set operation variables
my $strPathOp = $self->path_get($strPathType, $strPath);
# Set operation and debug strings
my $strOperation = OP_FILE_PATH_CREATE;
my $strDebug = " ${strPathType}:${strPathOp}, permission " . (defined($strMode) ? $strMode : '[undef]');
2014-06-21 22:17:09 +03:00
&log(DEBUG, "${strOperation}: ${strDebug}");
if ($self->is_remote($strPathType))
{
# Build param hash
my %oParamHash;
$oParamHash{path} = ${strPathOp};
if (defined($strMode))
2014-06-21 22:46:26 +03:00
{
$oParamHash{permission} = ${strMode};
2014-06-21 22:46:26 +03:00
}
2014-06-21 22:17:09 +03:00
# Add remote info to debug string
my $strRemote = 'remote (' . $self->{oRemote}->command_param_string(\%oParamHash) . ')';
2014-06-21 22:17:09 +03:00
$strDebug = "${strOperation}: ${strRemote}: ${strDebug}";
&log(TRACE, "${strOperation}: ${strRemote}");
# Execute the command
$self->{oRemote}->command_execute($strOperation, \%oParamHash, false, $strDebug);
}
else
{
2014-06-22 21:51:28 +03:00
if (!($bIgnoreExists && $self->exists($strPathType, $strPath)))
2014-06-21 22:46:26 +03:00
{
2014-06-22 21:51:28 +03:00
# Attempt the create the directory
my $stryError;
2014-06-21 22:17:09 +03:00
if (defined($strMode))
2014-06-21 22:17:09 +03:00
{
make_path($strPathOp, {mode => oct($strMode), error => \$stryError});
2014-06-21 22:17:09 +03:00
}
2014-06-22 21:51:28 +03:00
else
{
make_path($strPathOp, {error => \$stryError});
2014-06-22 21:51:28 +03:00
}
if (@$stryError)
2014-06-22 21:51:28 +03:00
{
# Capture the error
my $strError = "${strPathOp} could not be created: " . $!;
2014-06-21 22:17:09 +03:00
2014-06-22 21:51:28 +03:00
# If running on command line the return directly
if ($strPathType eq PATH_ABSOLUTE)
{
confess &log(ERROR, $strError, COMMAND_ERR_PATH_CREATE);
}
# Error the normal way
confess &log(ERROR, "${strDebug}: " . $strError); #, COMMAND_ERR_PATH_CREATE);
2014-06-22 21:51:28 +03:00
}
2014-06-21 22:17:09 +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
####################################################################################################################################
sub exists
{
my $self = shift;
my $strPathType = shift;
my $strPath = shift;
# Set operation variables
my $strPathOp = $self->path_get($strPathType, $strPath);
# Set operation and debug strings
my $strOperation = OP_FILE_EXISTS;
my $strDebug = "${strPathType}:${strPathOp}";
2014-06-21 22:17:09 +03:00
&log(DEBUG, "${strOperation}: ${strDebug}");
# Run remotely
if ($self->is_remote($strPathType))
{
# Build param hash
my %oParamHash;
2014-06-22 01:19:37 +03:00
$oParamHash{path} = $strPathOp;
2014-06-21 22:17:09 +03:00
# Add remote info to debug string
my $strRemote = 'remote (' . $self->{oRemote}->command_param_string(\%oParamHash) . ')';
2014-06-21 22:17:09 +03:00
$strDebug = "${strOperation}: ${strRemote}: ${strDebug}";
&log(TRACE, "${strOperation}: ${strRemote}");
# Execute the command
2014-06-21 22:17:09 +03:00
return $self->{oRemote}->command_execute($strOperation, \%oParamHash, true, $strDebug) eq 'Y';
}
# Run locally
else
{
# Stat the file/path to determine if it exists
my $oStat = lstat($strPathOp);
# Evaluate error
if (!defined($oStat))
{
# If the error is not entry missing, then throw error
if (!$!{ENOENT})
{
if ($strPathType eq PATH_ABSOLUTE)
{
confess &log(ERROR, $!, COMMAND_ERR_FILE_READ);
}
else
{
confess &log(ERROR, "${strDebug}: " . $!); #, COMMAND_ERR_FILE_READ);
}
}
return false;
}
}
return true;
}
2014-06-22 02:16:55 +03:00
####################################################################################################################################
2015-01-07 19:58:21 +02:00
# REMOVE
2014-06-22 02:16:55 +03:00
####################################################################################################################################
2015-01-07 19:58:21 +02:00
sub remove
2014-06-22 02:16:55 +03:00
{
my $self = shift;
my $strPathType = shift;
2015-01-07 19:58:21 +02:00
my $strPath = shift;
my $bTemp = shift;
my $bIgnoreMissing = shift;
2014-06-22 02:16:55 +03:00
2014-09-30 02:08:08 +03:00
# Set defaults
2015-01-07 19:58:21 +02:00
$bIgnoreMissing = defined($bIgnoreMissing) ? $bIgnoreMissing : true;
2014-09-30 02:08:08 +03:00
2014-06-22 02:16:55 +03:00
# Set operation variables
2015-01-07 19:58:21 +02:00
my $strPathOp = $self->path_get($strPathType, $strPath, $bTemp);
my $bRemoved = true;
2014-06-22 02:16:55 +03:00
# Set operation and debug strings
2015-01-07 19:58:21 +02:00
my $strOperation = OP_FILE_REMOVE;
my $strDebug = "${strPathType}:${strPathOp}";
2014-06-22 02:16:55 +03:00
&log(DEBUG, "${strOperation}: ${strDebug}");
2015-01-07 19:58:21 +02:00
# Run remotely
2014-06-22 02:16:55 +03:00
if ($self->is_remote($strPathType))
{
confess &log(ASSERT, "${strDebug}: remote operation not supported");
}
2015-01-07 19:58:21 +02:00
# Run locally
2014-06-22 02:16:55 +03:00
else
{
2015-01-07 19:58:21 +02:00
if (unlink($strPathOp) != 1)
2014-06-22 02:16:55 +03:00
{
2015-01-07 19:58:21 +02:00
$bRemoved = false;
2014-06-22 02:16:55 +03:00
2015-01-07 19:58:21 +02:00
my $strError = "${strPathOp} could not be removed: " . $!;
my $iErrorCode = COMMAND_ERR_PATH_READ;
if (!$self->exists($strPathType, $strPath))
2014-06-22 02:16:55 +03:00
{
2015-01-07 19:58:21 +02:00
$strError = "${strPathOp} does not exist";
$iErrorCode = COMMAND_ERR_PATH_MISSING;
2014-06-22 02:16:55 +03:00
}
2015-01-07 19:58:21 +02:00
if (!($iErrorCode == COMMAND_ERR_PATH_MISSING && $bIgnoreMissing))
2014-06-22 02:16:55 +03:00
{
2015-01-07 19:58:21 +02:00
if ($strPathType eq PATH_ABSOLUTE)
{
confess &log(ERROR, $strError, $iErrorCode);
}
2014-06-22 02:16:55 +03:00
2015-01-07 19:58:21 +02:00
confess &log(ERROR, "${strDebug}: " . $strError);
}
2014-06-22 02:16:55 +03:00
}
2015-01-07 19:58:21 +02:00
}
return $bRemoved;
}
####################################################################################################################################
# HASH
####################################################################################################################################
sub hash
{
my $self = shift;
my $strPathType = shift;
my $strFile = shift;
my $bCompressed = shift;
my $strHashType = shift;
# Set defaults
$bCompressed = defined($bCompressed) ? $bCompressed : false;
$strHashType = defined($strHashType) ? $strHashType : 'sha1';
# Set operation variables
my $strFileOp = $self->path_get($strPathType, $strFile);
my $strHash;
# Set operation and debug strings
my $strOperation = OP_FILE_HASH;
my $strDebug = "${strPathType}:${strFileOp}, " .
'compressed = ' . ($bCompressed ? 'true' : 'false') . ', ' .
"hash_type = ${strHashType}";
&log(DEBUG, "${strOperation}: ${strDebug}");
if ($self->is_remote($strPathType))
{
confess &log(ASSERT, "${strDebug}: remote operation not supported");
}
else
{
my $hFile;
if (!open($hFile, '<', $strFileOp))
{
my $strError = "${strFileOp} could not be read: " . $!;
my $iErrorCode = 2;
if (!$self->exists($strPathType, $strFile))
{
$strError = "${strFileOp} does not exist";
$iErrorCode = 1;
}
if ($strPathType eq PATH_ABSOLUTE)
{
confess &log(ERROR, $strError, $iErrorCode);
}
confess &log(ERROR, "${strDebug}: " . $strError);
}
my $oSHA = Digest::SHA->new($strHashType);
if ($bCompressed)
{
$oSHA->addfile($self->process_async_get()->process_begin('decompress', $hFile));
$self->process_async_get()->process_end();
}
else
{
$oSHA->addfile($hFile);
}
close($hFile);
$strHash = $oSHA->hexdigest();
}
return $strHash;
}
####################################################################################################################################
2015-01-07 19:58:21 +02:00
# OWNER
####################################################################################################################################
sub owner
{
my $self = shift;
my $strPathType = shift;
my $strFile = shift;
my $strUser = shift;
my $strGroup = shift;
# Set operation variables
my $strFileOp = $self->path_get($strPathType, $strFile);
2014-06-22 02:16:55 +03:00
2015-01-07 19:58:21 +02:00
# Set operation and debug strings
my $strOperation = OP_FILE_OWNER;
my $strDebug = "${strPathType}:${strFileOp}, " .
'user = ' . (defined($strUser) ? $strUser : '[undef]') .
'group = ' . (defined($strGroup) ? $strGroup : '[undef]');
&log(DEBUG, "${strOperation}: ${strDebug}");
2014-06-22 02:16:55 +03:00
2015-01-07 19:58:21 +02:00
if ($self->is_remote($strPathType))
{
confess &log(ASSERT, "${strDebug}: remote operation not supported");
}
else
{
my $iUserId;
my $iGroupId;
my $oStat;
if (!defined($strUser) || !defined($strGroup))
2014-09-30 02:08:08 +03:00
{
2015-01-07 19:58:21 +02:00
$oStat = stat($strFileOp);
if (!defined($oStat))
{
confess &log(ERROR, 'unable to stat ${strFileOp}');
}
}
if (defined($strUser))
{
$iUserId = getpwnam($strUser);
2014-09-30 02:08:08 +03:00
}
else
{
2015-01-07 19:58:21 +02:00
$iUserId = $oStat->uid;
2014-09-30 02:08:08 +03:00
}
2014-06-22 02:16:55 +03:00
2015-01-07 19:58:21 +02:00
if (defined($strGroup))
{
$iGroupId = getgrnam($strGroup);
}
else
{
$iGroupId = $oStat->gid;
}
2014-06-22 02:16:55 +03:00
2015-01-07 19:58:21 +02:00
chown($iUserId, $iGroupId, $strFileOp)
or confess &log(ERROR, "unable to set ownership for ${strFileOp}");
2014-06-22 02:16:55 +03:00
}
}
####################################################################################################################################
2014-06-22 01:19:37 +03:00
# LIST
####################################################################################################################################
2014-06-22 01:19:37 +03:00
sub list
{
my $self = shift;
2014-06-22 01:19:37 +03:00
my $strPathType = shift;
my $strPath = shift;
my $strExpression = shift;
my $strSortOrder = shift;
my $bIgnoreMissing = shift;
2014-02-03 03:03:05 +03:00
# Set defaults
2014-06-22 01:19:37 +03:00
$strSortOrder = defined($strSortOrder) ? $strSortOrder : 'forward';
$bIgnoreMissing = defined($bIgnoreMissing) ? $bIgnoreMissing : false;
2014-02-05 16:21:27 +03:00
2014-06-22 01:19:37 +03:00
# Set operation variables
my $strPathOp = $self->path_get($strPathType, $strPath);
my @stryFileList;
2014-02-03 03:03:05 +03:00
2014-06-22 01:19:37 +03:00
# Get the root path for the file list
my $strOperation = OP_FILE_LIST;
my $strDebug = "${strPathType}:${strPathOp}" .
', expression ' . (defined($strExpression) ? $strExpression : '[UNDEF]') .
2014-06-22 01:19:37 +03:00
", sort ${strSortOrder}";
&log(DEBUG, "${strOperation}: ${strDebug}");
2014-06-06 06:51:27 +03:00
2014-06-22 01:19:37 +03:00
# Run remotely
if ($self->is_remote($strPathType))
{
2014-06-22 01:19:37 +03:00
# Build param hash
my %oParamHash;
2014-06-22 01:19:37 +03:00
$oParamHash{path} = $strPathOp;
$oParamHash{sort_order} = $strSortOrder;
$oParamHash{ignore_missing} = ${bIgnoreMissing};
2014-06-22 01:19:37 +03:00
if (defined($strExpression))
{
$oParamHash{expression} = $strExpression;
}
2014-06-22 01:19:37 +03:00
# Add remote info to debug string
my $strRemote = 'remote (' . $self->{oRemote}->command_param_string(\%oParamHash) . ')';
2014-06-22 01:19:37 +03:00
$strDebug = "${strOperation}: ${strRemote}: ${strDebug}";
&log(TRACE, "${strOperation}: ${strRemote}");
2014-06-22 01:19:37 +03:00
# Execute the command
my $strOutput = $self->{oRemote}->command_execute($strOperation, \%oParamHash, false, $strDebug);
2014-06-22 03:08:49 +03:00
2014-06-22 01:19:37 +03:00
if (defined($strOutput))
{
@stryFileList = split(/\n/, $strOutput);
2014-06-16 13:12:38 +03:00
}
2014-06-06 06:51:27 +03:00
}
2014-06-22 01:19:37 +03:00
# Run locally
else
2014-02-03 03:03:05 +03:00
{
2014-06-22 01:19:37 +03:00
my $hPath;
2014-06-04 18:58:30 +03:00
2014-06-22 01:19:37 +03:00
if (!opendir($hPath, $strPathOp))
{
2014-06-22 01:19:37 +03:00
my $strError = "${strPathOp} could not be read: " . $!;
my $iErrorCode = COMMAND_ERR_PATH_READ;
2014-06-22 01:19:37 +03:00
if (!$self->exists($strPathType, $strPath))
{
# If ignore missing is set then return an empty array
if ($bIgnoreMissing)
{
return @stryFileList;
}
2014-06-22 01:19:37 +03:00
$strError = "${strPathOp} does not exist";
$iErrorCode = COMMAND_ERR_PATH_MISSING;
}
2014-06-22 01:19:37 +03:00
if ($strPathType eq PATH_ABSOLUTE)
{
2014-06-22 01:19:37 +03:00
confess &log(ERROR, $strError, $iErrorCode);
}
2014-06-22 01:19:37 +03:00
confess &log(ERROR, "${strDebug}: " . $strError);
}
2014-05-27 16:00:24 +03:00
2014-06-22 01:19:37 +03:00
@stryFileList = grep(!/^(\.)|(\.\.)$/i, readdir($hPath));
2014-06-22 01:19:37 +03:00
close($hPath);
2014-02-03 03:03:05 +03:00
2014-06-22 01:19:37 +03:00
if (defined($strExpression))
{
2014-06-22 01:19:37 +03:00
@stryFileList = grep(/$strExpression/i, @stryFileList);
}
2014-06-22 01:19:37 +03:00
# Reverse sort
2014-09-30 02:08:08 +03:00
if ($strSortOrder eq 'reverse')
{
2014-06-22 01:19:37 +03:00
@stryFileList = sort {$b cmp $a} @stryFileList;
2014-02-03 03:03:05 +03:00
}
2014-06-22 01:19:37 +03:00
# Normal sort
else
{
2014-06-22 01:19:37 +03:00
@stryFileList = sort @stryFileList;
}
2014-06-22 01:19:37 +03:00
}
2014-06-22 01:19:37 +03:00
# Return file list
return @stryFileList;
}
####################################################################################################################################
# WAIT
#
# Wait until the next second. This is done in the file object because it must be performed on whichever side the db is on, local or
# remote. This function is used to make sure that no files are copied in the same second as the manifest is created. The reason is
# that the db might modify they file again in the same second as the copy and that change will not be visible to a subsequent
# incremental backup using timestamp/size to determine deltas.
####################################################################################################################################
sub wait
{
my $self = shift;
my $strPathType = shift;
# Set operation and debug strings
my $strOperation = OP_FILE_WAIT;
my $strDebug = "${strPathType}";
&log(DEBUG, "${strOperation}: ${strDebug}");
# Second when the function was called
my $lTimeBegin;
# Run remotely
if ($self->is_remote($strPathType))
{
# Add remote info to debug string
$strDebug = "${strOperation}: remote: ${strDebug}";
&log(TRACE, "${strOperation}: remote");
# Execute the command
$lTimeBegin = $self->{oRemote}->command_execute($strOperation, undef, true, $strDebug);
}
# Run locally
else
{
# Wait the remainder of the current second
$lTimeBegin = gettimeofday();
my $lSleepMs = ceil(((int($lTimeBegin) + 1) - $lTimeBegin) * 1000);
usleep($lSleepMs * 1000);
&log(TRACE, "${strOperation}: slept ${lSleepMs}ms: begin ${lTimeBegin}, end " . gettimeofday());
$lTimeBegin = int($lTimeBegin);
}
return $lTimeBegin;
}
2014-06-22 01:19:37 +03:00
####################################################################################################################################
# MANIFEST
#
# 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
{
my $self = shift;
my $strPathType = shift;
my $strPath = shift;
my $oManifestHashRef = shift;
2014-06-22 01:19:37 +03:00
# Set operation variables
my $strPathOp = $self->path_get($strPathType, $strPath);
2014-06-22 01:19:37 +03:00
# Set operation and debug strings
my $strOperation = OP_FILE_MANIFEST;
2014-06-22 01:19:37 +03:00
my $strDebug = "${strPathType}:${strPathOp}";
&log(DEBUG, "${strOperation}: ${strDebug}");
2014-06-22 01:19:37 +03:00
# Run remotely
if ($self->is_remote($strPathType))
{
# Build param hash
my %oParamHash;
$oParamHash{path} = $strPathOp;
# Add remote info to debug string
my $strRemote = 'remote (' . $self->{oRemote}->command_param_string(\%oParamHash) . ')';
$strDebug = "${strOperation}: ${strRemote}: ${strDebug}";
&log(TRACE, "${strOperation}: ${strRemote}");
# Execute the command
data_hash_build($oManifestHashRef, $self->{oRemote}->command_execute($strOperation, \%oParamHash, true, $strDebug), "\t");
2014-02-03 03:03:05 +03:00
}
2014-06-22 01:19:37 +03:00
# Run locally
else
{
$self->manifest_recurse($strPathType, $strPathOp, undef, 0, $oManifestHashRef, $strDebug);
2014-06-22 01:19:37 +03:00
}
}
sub manifest_recurse
{
2014-06-22 03:08:49 +03:00
my $self = shift;
2014-06-22 01:19:37 +03:00
my $strPathType = shift;
my $strPathOp = shift;
my $strPathFileOp = shift;
my $iDepth = shift;
my $oManifestHashRef = shift;
my $strDebug = shift;
2014-06-22 01:19:37 +03:00
# Set operation and debug strings
$strDebug = $strDebug . (defined($strPathFileOp) ? " => ${strPathFileOp}" : '');
my $strPathRead = $strPathOp . (defined($strPathFileOp) ? "/${strPathFileOp}" : '');
2014-06-22 01:19:37 +03:00
my $hPath;
# Open the path
2014-06-22 01:19:37 +03:00
if (!opendir($hPath, $strPathRead))
{
my $strError = "${strPathRead} could not be read: " . $!;
2014-06-22 03:08:49 +03:00
my $iErrorCode = COMMAND_ERR_PATH_READ;
2014-06-22 01:19:37 +03:00
# If the path does not exist and is not the root path requested then return, else error
# It's OK for paths to go away during execution (databases are a dynamic thing!)
if (!$self->exists(PATH_ABSOLUTE, $strPathRead))
2014-06-22 01:19:37 +03:00
{
if ($iDepth != 0)
{
return;
}
2014-06-22 01:19:37 +03:00
$strError = "${strPathRead} does not exist";
2014-06-22 03:08:49 +03:00
$iErrorCode = COMMAND_ERR_PATH_MISSING;
2014-06-22 01:19:37 +03:00
}
if ($strPathType eq PATH_ABSOLUTE)
{
2014-06-22 03:08:49 +03:00
confess &log(ERROR, $strError, $iErrorCode);
2014-06-22 01:19:37 +03:00
}
confess &log(ERROR, "${strDebug}: " . $strError);
2014-06-22 01:19:37 +03:00
}
# Get a list of all files in the path (except ..)
2014-06-22 01:19:37 +03:00
my @stryFileList = grep(!/^\..$/i, readdir($hPath));
close($hPath);
# Loop through all subpaths/files in the path
2014-06-22 01:19:37 +03:00
foreach my $strFile (@stryFileList)
{
my $strPathFile = "${strPathRead}/$strFile";
my $bCurrentDir = $strFile eq '.';
2014-06-22 01:19:37 +03:00
# Create the file and path names
2014-06-22 01:19:37 +03:00
if ($iDepth != 0)
{
if ($bCurrentDir)
{
$strFile = $strPathFileOp;
$strPathFile = $strPathRead;
}
else
{
$strFile = "${strPathFileOp}/${strFile}";
}
}
# Stat the path/file
2014-06-22 01:19:37 +03:00
my $oStat = lstat($strPathFile);
# Check for errors in stat
2014-06-22 01:19:37 +03:00
if (!defined($oStat))
{
2014-06-22 03:08:49 +03:00
my $strError = "${strPathFile} could not be read: " . $!;
my $iErrorCode = COMMAND_ERR_FILE_READ;
2014-06-22 01:19:37 +03:00
# If the file does not exist then go to the next file, else error
# It's OK for files to go away during execution (databases are a dynamic thing!)
if (!$self->exists(PATH_ABSOLUTE, $strPathFile))
2014-06-22 03:08:49 +03:00
{
next;
2014-06-22 03:08:49 +03:00
}
2014-06-22 01:19:37 +03:00
2014-06-22 03:08:49 +03:00
if ($strPathType eq PATH_ABSOLUTE)
{
confess &log(ERROR, $strError, $iErrorCode);
2014-06-22 01:19:37 +03:00
}
confess &log(ERROR, "${strDebug}: " . $strError);
2014-06-22 01:19:37 +03:00
}
# Check for regular file
if (S_ISREG($oStat->mode))
{
${$oManifestHashRef}{name}{"${strFile}"}{type} = 'f';
2014-06-22 01:19:37 +03:00
# 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';
2014-06-22 01:19:37 +03:00
}
# Check for link
elsif (S_ISLNK($oStat->mode))
{
${$oManifestHashRef}{name}{"${strFile}"}{type} = 'l';
2014-06-22 01:19:37 +03:00
# Get link destination
${$oManifestHashRef}{name}{"${strFile}"}{link_destination} = readlink($strPathFile);
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, "${strDebug}: " . $strError);
2014-06-22 01:19:37 +03:00
}
}
}
# Not a recognized type
2014-06-22 01:19:37 +03:00
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, "${strDebug}: " . $strError);
2014-06-22 01:19:37 +03:00
}
# 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')
2014-06-22 01:19:37 +03:00
{
${$oManifestHashRef}{name}{"${strFile}"}{permission} = sprintf('%04o', S_IMODE($oStat->mode));
2014-06-22 01:19:37 +03:00
}
# Recurse into directories
if (${$oManifestHashRef}{name}{"${strFile}"}{type} eq 'd' && !$bCurrentDir)
2014-06-22 01:19:37 +03:00
{
$self->manifest_recurse($strPathType, $strPathOp, $strFile, $iDepth + 1, $oManifestHashRef, $strDebug);
2014-06-22 01:19:37 +03:00
}
}
}
####################################################################################################################################
# COPY
#
# Copies a file from one location to another:
#
# * 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, mode, and ownership can be set on destination file
2014-06-22 01:19:37 +03:00
# * destination path can optionally be created
####################################################################################################################################
sub copy
{
my $self = shift;
my $strSourcePathType = shift;
my $strSourceFile = shift;
my $strDestinationPathType = shift;
my $strDestinationFile = shift;
my $bSourceCompressed = shift;
my $bDestinationCompress = shift;
my $bIgnoreMissingSource = shift;
my $lModificationTime = shift;
my $strMode = shift;
2014-06-24 01:54:00 +03:00
my $bDestinationPathCreate = shift;
my $strUser = shift;
my $strGroup = shift;
2014-06-22 01:19:37 +03:00
# Set defaults
$bSourceCompressed = defined($bSourceCompressed) ? $bSourceCompressed : false;
$bDestinationCompress = defined($bDestinationCompress) ? $bDestinationCompress : false;
$bIgnoreMissingSource = defined($bIgnoreMissingSource) ? $bIgnoreMissingSource : false;
2014-06-24 01:54:00 +03:00
$bDestinationPathCreate = defined($bDestinationPathCreate) ? $bDestinationPathCreate : false;
2014-06-22 01:19:37 +03:00
# Set working variables
my $bSourceRemote = $self->is_remote($strSourcePathType) || $strSourcePathType eq PIPE_STDIN;
my $bDestinationRemote = $self->is_remote($strDestinationPathType) || $strDestinationPathType eq PIPE_STDOUT;
my $strSourceOp = $strSourcePathType eq PIPE_STDIN ?
$strSourcePathType : $self->path_get($strSourcePathType, $strSourceFile);
my $strDestinationOp = $strDestinationPathType eq PIPE_STDOUT ?
$strDestinationPathType : $self->path_get($strDestinationPathType, $strDestinationFile);
my $strDestinationTmpOp = $strDestinationPathType eq PIPE_STDOUT ?
undef : $self->path_get($strDestinationPathType, $strDestinationFile, true);
# Set debug string and log
my $strDebug = ($bSourceRemote ? ' remote' : ' local') . " ${strSourcePathType}" .
(defined($strSourceFile) ? ":${strSourceOp}" : '') .
' to' . ($bDestinationRemote ? ' remote' : ' local') . " ${strDestinationPathType}" .
(defined($strDestinationFile) ? ":${strDestinationOp}" : '') .
', source_compressed = ' . ($bSourceCompressed ? 'true' : 'false') .
', destination_compress = ' . ($bDestinationCompress ? 'true' : 'false') .
', ignore_missing_source = ' . ($bIgnoreMissingSource ? 'true' : 'false') .
', destination_path_create = ' . ($bDestinationPathCreate ? 'true' : 'false') .
', modification_time = ' . (defined($lModificationTime) ? $lModificationTime : '[undef]') .
', mode = ' . (defined($strMode) ? $strMode : '[undef]') .
', user = ' . (defined($strUser) ? $strUser : '[undef]') .
2014-12-31 18:20:46 +02:00
', group = ' . (defined($strGroup) ? $strGroup : '[undef]');
2014-06-22 01:19:37 +03:00
&log(DEBUG, OP_FILE_COPY . ": ${strDebug}");
# Open the source and destination files (if needed)
my $hSourceFile;
my $hDestinationFile;
if (!$bSourceRemote)
{
if (!open($hSourceFile, '<', $strSourceOp))
2014-06-22 01:19:37 +03:00
{
my $strError = $!;
my $iErrorCode = COMMAND_ERR_FILE_READ;
if ($!{ENOENT})
{
# $strError = 'file is missing';
$iErrorCode = COMMAND_ERR_FILE_MISSING;
if ($bIgnoreMissingSource && $strDestinationPathType ne PIPE_STDOUT)
{
return false;
}
}
$strError = "cannot open source file ${strSourceOp}: " . $strError;
if ($strSourcePathType eq PATH_ABSOLUTE)
{
if ($strDestinationPathType eq PIPE_STDOUT)
{
$self->{oRemote}->write_line(*STDOUT, 'block 0');
2014-06-22 01:19:37 +03:00
}
2014-06-22 03:08:49 +03:00
2014-06-22 01:19:37 +03:00
confess &log(ERROR, $strError, $iErrorCode);
}
confess &log(ERROR, "${strDebug}: " . $strError, $iErrorCode);
}
}
if (!$bDestinationRemote)
{
2014-06-24 01:54:00 +03:00
# Open the destination temp file
if (!open($hDestinationFile, '>', $strDestinationTmpOp))
2014-06-24 01:54:00 +03:00
{
my $strError = "${strDestinationTmpOp} could not be opened: " . $!;
my $iErrorCode = COMMAND_ERR_FILE_READ;
2014-06-28 21:32:34 +03:00
if (!$self->exists(PATH_ABSOLUTE, dirname($strDestinationTmpOp)))
2014-06-24 01:54:00 +03:00
{
$strError = dirname($strDestinationTmpOp) . ' does not exist';
2014-06-24 01:54:00 +03:00
$iErrorCode = COMMAND_ERR_FILE_MISSING;
}
if (!($bDestinationPathCreate && $iErrorCode == COMMAND_ERR_FILE_MISSING))
{
if ($strSourcePathType eq PATH_ABSOLUTE)
{
confess &log(ERROR, $strError, $iErrorCode);
}
confess &log(ERROR, "${strDebug}: " . $strError);
}
2014-06-28 21:32:34 +03:00
$self->path_create(PATH_ABSOLUTE, dirname($strDestinationTmpOp));
2014-06-24 01:54:00 +03:00
if (!open($hDestinationFile, '>', $strDestinationTmpOp))
2014-06-24 01:54:00 +03:00
{
confess &log(ERROR, "unable to open destination file ${strDestinationOp}: " . $!);
}
}
2014-06-22 01:19:37 +03:00
}
# If source or destination are remote
if ($bSourceRemote || $bDestinationRemote)
{
# Build the command and open the local file
my $hFile;
my %oParamHash;
my $hIn,
my $hOut;
my $strRemote;
my $strOperation;
# If source is remote and destination is local
if ($bSourceRemote && !$bDestinationRemote)
{
$hOut = $hDestinationFile;
$strOperation = OP_FILE_COPY_OUT;
$strRemote = 'in';
if ($strSourcePathType eq PIPE_STDIN)
{
$hIn = *STDIN;
}
else
{
$oParamHash{source_file} = $strSourceOp;
$oParamHash{source_compressed} = $bSourceCompressed;
$hIn = $self->{oRemote}->{hOut};
}
}
# Else if source is local and destination is remote
elsif (!$bSourceRemote && $bDestinationRemote)
{
$hIn = $hSourceFile;
$strOperation = OP_FILE_COPY_IN;
$strRemote = 'out';
if ($strDestinationPathType eq PIPE_STDOUT)
{
$hOut = *STDOUT;
}
else
{
$oParamHash{destination_file} = $strDestinationOp;
$oParamHash{destination_compress} = $bDestinationCompress;
$oParamHash{destination_path_create} = $bDestinationPathCreate;
2014-06-22 01:19:37 +03:00
if (defined($strMode))
{
$oParamHash{permission} = $strMode;
}
if (defined($strUser))
{
$oParamHash{user} = $strUser;
}
if (defined($strGroup))
2014-06-28 21:32:34 +03:00
{
$oParamHash{group} = $strGroup;
2014-06-28 21:32:34 +03:00
}
2014-06-22 01:19:37 +03:00
$hOut = $self->{oRemote}->{hIn};
}
}
# Else source and destination are remote
else
{
$strOperation = OP_FILE_COPY;
$oParamHash{source_file} = $strSourceOp;
$oParamHash{source_compressed} = $bSourceCompressed;
$oParamHash{destination_file} = $strDestinationOp;
$oParamHash{destination_compress} = $bDestinationCompress;
$oParamHash{destination_path_create} = $bDestinationPathCreate;
2014-06-22 01:19:37 +03:00
if (defined($strMode))
{
$oParamHash{permission} = $strMode;
}
if (defined($strUser))
2014-06-28 21:32:34 +03:00
{
$oParamHash{user} = $strUser;
}
if (defined($strGroup))
{
$oParamHash{group} = $strGroup;
2014-06-28 21:32:34 +03:00
}
2014-06-22 01:19:37 +03:00
if ($bIgnoreMissingSource)
{
$oParamHash{ignore_missing_source} = $bIgnoreMissingSource;
}
}
# Build debug string
if (%oParamHash)
{
my $strRemote = 'remote (' . $self->{oRemote}->command_param_string(\%oParamHash) . ')';
2014-06-22 01:19:37 +03:00
$strDebug = "${strOperation}: ${strRemote}: ${strDebug}";
&log(TRACE, "${strOperation}: ${strRemote}");
}
# If an operation is defined then write it
if (%oParamHash)
{
$self->{oRemote}->command_write($strOperation, \%oParamHash);
}
# Transfer the file (skip this for copies where both sides are remote)
if ($strOperation ne OP_FILE_COPY)
{
$self->{oRemote}->binary_xfer($hIn, $hOut, $strRemote, $bSourceCompressed, $bDestinationCompress);
}
# If this is the controlling process then wait for OK from remote
if (%oParamHash)
{
# Test for an error when reading output
my $strOutput;
2014-06-22 03:08:49 +03:00
2014-06-22 01:19:37 +03:00
eval
{
$strOutput = $self->{oRemote}->output_read($strOperation eq OP_FILE_COPY, $strDebug, true);
2014-06-22 01:19:37 +03:00
};
2014-06-22 03:08:49 +03:00
2014-06-22 01:19:37 +03:00
# If there is an error then evaluate
if ($@)
{
my $oMessage = $@;
2014-06-22 03:08:49 +03:00
2014-06-22 01:19:37 +03:00
# We'll ignore this error if the source file was missing and missing file exception was returned
# and bIgnoreMissingSource is set
if ($bIgnoreMissingSource && $strRemote eq 'in' && $oMessage->isa('BackRest::Exception') &&
2014-06-22 01:19:37 +03:00
$oMessage->code() == COMMAND_ERR_FILE_MISSING)
{
close($hDestinationFile) or confess &log(ERROR, "cannot close file ${strDestinationTmpOp}");
unlink($strDestinationTmpOp) or confess &log(ERROR, "cannot remove file ${strDestinationTmpOp}");
return false;
}
2014-12-31 18:20:46 +02:00
# Otherwise report the error. I don't like this method of error reporting - raising the exception object makes the
# error hard to interpret and hides the error stack, raising test loses the error code.
if ($oMessage->isa('BackRest::Exception'))
{
confess $oMessage->message();
}
else
{
confess $oMessage;
}
2014-06-22 01:19:37 +03:00
}
# If this was a remote copy, then return the result
if ($strOperation eq OP_FILE_COPY)
{
return false; #$strOutput eq 'N' ? true : false;
}
}
}
# Else this is a local operation
else
2014-02-05 19:35:09 +03:00
{
# If the source is compressed and the destination is not then decompress
if ($bSourceCompressed && !$bDestinationCompress)
{
gunzip($hSourceFile => $hDestinationFile)
or die confess &log(ERROR, "${strDebug}: unable to uncompress: " . $GunzipError);
}
elsif (!$bSourceCompressed && $bDestinationCompress)
{
gzip($hSourceFile => $hDestinationFile)
or die confess &log(ERROR, "${strDebug}: unable to compress: " . $GzipError);
}
else
{
cp($hSourceFile, $hDestinationFile)
or die confess &log(ERROR, "${strDebug}: unable to copy: " . $!);
}
2014-02-05 19:35:09 +03:00
}
2014-02-07 00:37:37 +03:00
# Close the source file (if open)
2014-06-06 06:51:27 +03:00
if (defined($hSourceFile))
{
close($hSourceFile) or confess &log(ERROR, "cannot close file ${strSourceOp}");
}
# Close the destination file (if open)
2014-06-06 06:51:27 +03:00
if (defined($hDestinationFile))
{
close($hDestinationFile) or confess &log(ERROR, "cannot close file ${strDestinationTmpOp}");
}
# Where the destination is local, set permissions, modification time, and perform move to final location
if (!$bDestinationRemote)
2014-02-05 19:35:09 +03:00
{
# Set the file permission if required
if (defined($strMode))
{
chmod(oct($strMode), $strDestinationTmpOp)
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
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
2015-01-07 19:58:21 +02:00
# set user and/or group if required
if (defined($strUser) || defined($strGroup))
{
2015-01-07 19:58:21 +02:00
$self->owner(PATH_ABSOLUTE, $strDestinationTmpOp, $strUser, $strGroup);
}
2014-06-22 01:19:37 +03:00
# Move the file from tmp to final destination
2014-06-28 21:32:34 +03:00
$self->move(PATH_ABSOLUTE, $strDestinationTmpOp, PATH_ABSOLUTE, $strDestinationOp, true);
2014-02-03 03:03:05 +03:00
}
2014-02-07 00:37:37 +03:00
2014-06-22 01:19:37 +03:00
return true;
2014-02-03 03:03:05 +03:00
}
2014-06-02 00:23:33 +03:00
2014-10-10 22:13:28 +03:00
1;