mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2025-01-18 04:58:51 +02:00
Fixed a regression introduced in v1.13 that could cause backups to fail.
This happened if files were removed (e.g. tables dropped) while the manifest was being built. Reported by Navid Golpayegani.
This commit is contained in:
parent
845c6112bf
commit
e2ac7e1ea6
@ -62,6 +62,11 @@
|
||||
<contributor-id type="github">sfrost</contributor-id>
|
||||
</contributor>
|
||||
|
||||
<contributor id="golpayegani.navid">
|
||||
<contributor-name-display>Navid Golpayegani</contributor-name-display>
|
||||
<contributor-id type="github">golpa</contributor-id>
|
||||
</contributor>
|
||||
|
||||
<contributor id="gunduz.devrim">
|
||||
<contributor-name-display>Devrim G&uuml;nd&uuml;z</contributor-name-display>
|
||||
<contributor-id type="github">devrimgunduz</contributor-id>
|
||||
@ -148,6 +153,34 @@
|
||||
</contributor-list>
|
||||
|
||||
<release-list>
|
||||
<release date="XXXX-XX-XX" version="1.15dev" title="UNDER DEVELOPMENT">
|
||||
<release-core-list>
|
||||
<release-bug-list>
|
||||
<release-item>
|
||||
<release-item-contributor-list>
|
||||
<release-item-ideator id="golpayegani.navid"/>
|
||||
</release-item-contributor-list>
|
||||
|
||||
<p>Fixed a regression introduced in <id>v1.13</id> that could cause backups to fail if files were removed (e.g. tables dropped) while the manifest was being built.</p>
|
||||
</release-item>
|
||||
</release-bug-list>
|
||||
|
||||
<release-refactor-list>
|
||||
<release-item>
|
||||
<p>Refactor <code>FileCommon::fileManifest()</code> and <code>FileCommon::fileStat</code> to be more modular to allow complete branch/statement level coverage testing.</p>
|
||||
</release-item>
|
||||
</release-refactor-list>
|
||||
</release-core-list>
|
||||
|
||||
<release-test-list>
|
||||
<release-feature-list>
|
||||
<release-item>
|
||||
<p>Complete branch/statement level coverage testing for <code>FileCommon::fileManifest()</code> and <code>FileCommon::fileStat</code> functions and helper functions.</p>
|
||||
</release-item>
|
||||
</release-feature-list>
|
||||
</release-test-list>
|
||||
</release>
|
||||
|
||||
<release date="2017-02-13" version="1.14" title="Bug Fixes">
|
||||
<release-core-list>
|
||||
<release-bug-list>
|
||||
|
@ -528,16 +528,11 @@ push @EXPORT, qw(logException);
|
||||
####################################################################################################################################
|
||||
sub logErrorResult
|
||||
{
|
||||
my $oResult = shift;
|
||||
my $iCode = shift;
|
||||
my $strMessage = shift;
|
||||
my $strResult = shift;
|
||||
|
||||
if (!$oResult)
|
||||
{
|
||||
confess &log(ERROR, $strMessage . (defined($!) ? ": $!" : ''), $iCode);
|
||||
}
|
||||
|
||||
return $oResult;
|
||||
confess &log(ERROR, $strMessage . (defined($strResult) ? ": $strResult" : ''), $iCode);
|
||||
}
|
||||
|
||||
push @EXPORT, qw(logErrorResult);
|
||||
|
@ -211,6 +211,45 @@ sub fileHashSize
|
||||
|
||||
push @EXPORT, qw(fileHashSize);
|
||||
|
||||
####################################################################################################################################
|
||||
# fileLinkDestination
|
||||
#
|
||||
# Return the destination of a symlink.
|
||||
####################################################################################################################################
|
||||
sub fileLinkDestination
|
||||
{
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$strLink,
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '::fileLinkDestination', \@_,
|
||||
{name => 'strFile', trace => true},
|
||||
);
|
||||
|
||||
# Get link destination
|
||||
my $strLinkDestination = readlink($strLink);
|
||||
|
||||
# Check for errors
|
||||
if (!defined($strLinkDestination))
|
||||
{
|
||||
logErrorResult(
|
||||
$OS_ERROR{ENOENT} ? ERROR_FILE_MISSING : ERROR_FILE_OPEN, "unable to get destination for link ${strLink}", $OS_ERROR);
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'strLinkDestination', value => $strLinkDestination, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
push @EXPORT, qw(fileLinkDestination);
|
||||
|
||||
####################################################################################################################################
|
||||
# fileList
|
||||
#
|
||||
@ -350,35 +389,12 @@ sub fileManifestRecurse
|
||||
$strPathRead = dirname($strPathRead);
|
||||
}
|
||||
|
||||
# Open the path
|
||||
if (!opendir($hPath, $strPathRead))
|
||||
{
|
||||
my $strError = "${strPathRead} could not be read: " . $!;
|
||||
my $iErrorCode = ERROR_PATH_OPEN;
|
||||
|
||||
# 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 (!fileExists($strPathRead))
|
||||
{
|
||||
if ($iDepth != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$strError = "${strPathRead} does not exist";
|
||||
$iErrorCode = ERROR_PATH_MISSING;
|
||||
}
|
||||
|
||||
confess &log(ERROR, $strError, $iErrorCode);
|
||||
}
|
||||
|
||||
# Get a list of all files in the path (except ..)
|
||||
my @stryFileList = grep(!/^\..$/i, readdir($hPath));
|
||||
|
||||
close($hPath);
|
||||
# Get a list of all files in the path (including .)
|
||||
my @stryFileList = fileList($strPathRead, undef, undef, $iDepth != 0);
|
||||
unshift(@stryFileList, '.');
|
||||
|
||||
# Loop through all subpaths/files in the path
|
||||
foreach my $strFile (sort(@stryFileList))
|
||||
foreach my $strFile (@stryFileList)
|
||||
{
|
||||
# Skip this file if it does not match the filter
|
||||
if (defined($strFilter) && $strFile ne $strFilter)
|
||||
@ -403,77 +419,7 @@ sub fileManifestRecurse
|
||||
}
|
||||
}
|
||||
|
||||
# Stat the path/file
|
||||
my $oStat = lstat($strPathFile);
|
||||
|
||||
# Check for errors in stat
|
||||
if (!defined($oStat))
|
||||
{
|
||||
my $strError = "${strPathFile} could not be read: " . $!;
|
||||
my $iErrorCode = ERROR_FILE_READ;
|
||||
|
||||
# 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 (fileExists($strPathFile))
|
||||
{
|
||||
next;
|
||||
}
|
||||
|
||||
confess &log(ERROR, $strError, $iErrorCode);
|
||||
}
|
||||
|
||||
# Check for regular file
|
||||
if (S_ISREG($oStat->mode))
|
||||
{
|
||||
$hManifest->{$strFile}{type} = 'f';
|
||||
|
||||
# Get inode
|
||||
$hManifest->{$strFile}{inode} = $oStat->ino;
|
||||
|
||||
# Get size
|
||||
$hManifest->{$strFile}{size} = $oStat->size;
|
||||
|
||||
# Get modification time
|
||||
$hManifest->{$strFile}{modification_time} = $oStat->mtime;
|
||||
}
|
||||
# Check for directory
|
||||
elsif (S_ISDIR($oStat->mode))
|
||||
{
|
||||
$hManifest->{$strFile}{type} = 'd';
|
||||
}
|
||||
# Check for link
|
||||
elsif (S_ISLNK($oStat->mode))
|
||||
{
|
||||
$hManifest->{$strFile}{type} = 'l';
|
||||
|
||||
# Get link destination
|
||||
$hManifest->{$strFile}{link_destination} = readlink($strPathFile);
|
||||
|
||||
if (!defined($hManifest->{$strFile}{link_destination}))
|
||||
{
|
||||
if (-e $strPathFile)
|
||||
{
|
||||
confess &log(ERROR, "${strPathFile} error reading link: " . $!, ERROR_LINK_OPEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
# Not a recognized type
|
||||
else
|
||||
{
|
||||
confess &log(ERROR, "${strPathFile} is not of type directory, file, or link", ERROR_FILE_INVALID);
|
||||
}
|
||||
|
||||
# Get user name
|
||||
$hManifest->{$strFile}{user} = getpwuid($oStat->uid);
|
||||
|
||||
# Get group name
|
||||
$hManifest->{$strFile}{group} = getgrgid($oStat->gid);
|
||||
|
||||
# Get mode
|
||||
if ($hManifest->{$strFile}{type} ne 'l')
|
||||
{
|
||||
$hManifest->{$strFile}{mode} = sprintf('%04o', S_IMODE($oStat->mode));
|
||||
}
|
||||
$hManifest->{$strFile} = fileManifestStat($strPathFile);
|
||||
|
||||
# Recurse into directories
|
||||
if ($hManifest->{$strFile}{type} eq 'd' && !$bCurrentDir)
|
||||
@ -486,6 +432,77 @@ sub fileManifestRecurse
|
||||
return logDebugReturn($strOperation);
|
||||
}
|
||||
|
||||
sub fileManifestStat
|
||||
{
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$strFile,
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '::fileManifestRecurse', \@_,
|
||||
{name => 'strFile', trace => true},
|
||||
);
|
||||
|
||||
# Stat the path/file, ignoring any that are missing
|
||||
my $oStat = fileStat($strFile, true);
|
||||
|
||||
# Generate file data if stat succeeded (i.e. file exists)
|
||||
my $hFile;
|
||||
|
||||
if (defined($oStat))
|
||||
{
|
||||
# Check for regular file
|
||||
if (S_ISREG($oStat->mode))
|
||||
{
|
||||
$hFile->{type} = 'f';
|
||||
|
||||
# Get size
|
||||
$hFile->{size} = $oStat->size;
|
||||
|
||||
# Get modification time
|
||||
$hFile->{modification_time} = $oStat->mtime;
|
||||
}
|
||||
# Check for directory
|
||||
elsif (S_ISDIR($oStat->mode))
|
||||
{
|
||||
$hFile->{type} = 'd';
|
||||
}
|
||||
# Check for link
|
||||
elsif (S_ISLNK($oStat->mode))
|
||||
{
|
||||
$hFile->{type} = 'l';
|
||||
$hFile->{link_destination} = fileLinkDestination($strFile);
|
||||
}
|
||||
# Not a recognized type
|
||||
else
|
||||
{
|
||||
confess &log(ERROR, "${strFile} is not of type directory, file, or link", ERROR_FILE_INVALID);
|
||||
}
|
||||
|
||||
# Get user name
|
||||
$hFile->{user} = getpwuid($oStat->uid);
|
||||
|
||||
# Get group name
|
||||
$hFile->{group} = getgrgid($oStat->gid);
|
||||
|
||||
# Get mode
|
||||
if ($hFile->{type} ne 'l')
|
||||
{
|
||||
$hFile->{mode} = sprintf('%04o', S_IMODE($oStat->mode));
|
||||
}
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
$strOperation,
|
||||
{name => 'hFile', value => $hFile, trace => true}
|
||||
);
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
# fileMode
|
||||
#
|
||||
@ -786,22 +803,26 @@ sub fileStat
|
||||
my
|
||||
(
|
||||
$strOperation,
|
||||
$strFile
|
||||
$strFile,
|
||||
$bIgnoreMissing,
|
||||
) =
|
||||
logDebugParam
|
||||
(
|
||||
__PACKAGE__ . '::fileStat', \@_,
|
||||
{name => 'strFile', required => true, trace => true}
|
||||
{name => 'strFile', trace => true},
|
||||
{name => 'bIgnoreMissing', default => false, trace => true},
|
||||
);
|
||||
|
||||
# Stat the file/path to determine if it exists
|
||||
# Stat the file/path
|
||||
my $oStat = lstat($strFile);
|
||||
|
||||
# Evaluate error
|
||||
# Check for errors
|
||||
if (!defined($oStat))
|
||||
{
|
||||
my $strError = $!;
|
||||
confess &log(ERROR, "unable to read ${strFile}" . (defined($strError) ? ": $strError" : ''), ERROR_FILE_OPEN);
|
||||
if (!($OS_ERROR{ENOENT} && $bIgnoreMissing))
|
||||
{
|
||||
logErrorResult($OS_ERROR{ENOENT} ? ERROR_FILE_MISSING : ERROR_FILE_OPEN, "unable to stat ${strFile}", $OS_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
# Return from function and log return values if any
|
||||
|
@ -35,7 +35,7 @@ use constant BACKREST_BIN => abs_path(
|
||||
# Defines the current version of the BackRest executable. The version number is used to track features but does not affect what
|
||||
# repositories or manifests can be read - that's the job of the format number.
|
||||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||||
use constant BACKREST_VERSION => '1.14';
|
||||
use constant BACKREST_VERSION => '1.15dev';
|
||||
push @EXPORT, qw(BACKREST_VERSION);
|
||||
|
||||
# Format Format Number
|
||||
|
@ -11,7 +11,7 @@ use AutoLoader;
|
||||
our @ISA = qw(Exporter);
|
||||
|
||||
# Library version (add .999 during development)
|
||||
our $VERSION = '1.14';
|
||||
our $VERSION = '1.15.999';
|
||||
|
||||
sub libCVersion {return $VERSION};
|
||||
|
||||
|
@ -1335,7 +1335,7 @@ P00 WARN: incr backup cannot alter 'checksum-page' option to 'false', reset to
|
||||
P00 ERROR: [146]: tablespace symlink ../invalid_tblspc destination must not be in $PGDATA
|
||||
P00 INFO: backup command end: aborted with exception [146]
|
||||
|
||||
incr backup - $PGDATA is a substring of valid tblspc excluding / (file open err expected) (db-master host)
|
||||
incr backup - $PGDATA is a substring of valid tblspc excluding / (file missing err expected) (db-master host)
|
||||
> [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --no-online --log-level-console=detail --stanza=db backup
|
||||
------------------------------------------------------------------------------------------------------------------------------------
|
||||
P00 INFO: backup command begin [BACKREST-VERSION]: --no-compress --config=[TEST_PATH]/db-master/pgbackrest.conf --db-path=[TEST_PATH]/db-master/db/base --lock-path=[TEST_PATH]/db-master/repo/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/repo/log --no-online --repo-path=[TEST_PATH]/db-master/repo --stanza=db --start-fast
|
||||
@ -1343,8 +1343,8 @@ P00 WARN: option retention-full is not set, the repository may run out of spac
|
||||
HINT: to retain full backups indefinitely (without warning), set option 'retention-full' to the maximum.
|
||||
P00 INFO: last backup label = [BACKUP-FULL-2], version = [VERSION-1]
|
||||
P00 WARN: incr backup cannot alter 'checksum-page' option to 'false', reset to 'true' from [BACKUP-FULL-2]
|
||||
P00 ERROR: [116]: unable to read [TEST_PATH]/db-master/db/base_tbs: No such file or directory
|
||||
P00 INFO: backup command end: aborted with exception [116]
|
||||
P00 ERROR: [130]: unable to stat [TEST_PATH]/db-master/db/base_tbs: No such file or directory
|
||||
P00 INFO: backup command end: aborted with exception [130]
|
||||
|
||||
incr backup - invalid tablespace in $PGDATA (db-master host)
|
||||
> [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --no-online --log-level-console=detail --stanza=db backup
|
||||
|
@ -152,9 +152,19 @@ my $oTestDef =
|
||||
&TESTDEF_TEST_TOTAL => 2,
|
||||
&TESTDEF_TEST_INDIVIDUAL => false,
|
||||
},
|
||||
{
|
||||
&TESTDEF_TEST_NAME => 'link',
|
||||
&TESTDEF_TEST_TOTAL => 1,
|
||||
&TESTDEF_TEST_INDIVIDUAL => false,
|
||||
},
|
||||
{
|
||||
&TESTDEF_TEST_NAME => 'stat',
|
||||
&TESTDEF_TEST_TOTAL => 1,
|
||||
&TESTDEF_TEST_INDIVIDUAL => false,
|
||||
},
|
||||
{
|
||||
&TESTDEF_TEST_NAME => 'manifest',
|
||||
&TESTDEF_TEST_TOTAL => 8,
|
||||
&TESTDEF_TEST_TOTAL => 4,
|
||||
&TESTDEF_TEST_INDIVIDUAL => false,
|
||||
},
|
||||
{
|
||||
|
@ -55,6 +55,9 @@ sub initModule
|
||||
$self->host(),
|
||||
$self->backrestUser(),
|
||||
HOST_PROTOCOL_TIMEOUT);
|
||||
|
||||
rmdir($strRepoPath)
|
||||
or confess "Unable to remove repo directory: ${strRepoPath}";
|
||||
}
|
||||
|
||||
####################################################################################################################################
|
||||
|
58
test/lib/pgBackRestTest/File/FileLinkTest.pm
Normal file
58
test/lib/pgBackRestTest/File/FileLinkTest.pm
Normal file
@ -0,0 +1,58 @@
|
||||
####################################################################################################################################
|
||||
# FileLinkTest.pm - Tests for FileCommon::fileLinkDestination
|
||||
####################################################################################################################################
|
||||
package pgBackRestTest::File::FileLinkTest;
|
||||
use parent 'pgBackRestTest::File::FileCommonTest';
|
||||
|
||||
####################################################################################################################################
|
||||
# Perl includes
|
||||
####################################################################################################################################
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess);
|
||||
use English '-no_match_vars';
|
||||
|
||||
use Fcntl qw(:mode);
|
||||
use File::stat;
|
||||
|
||||
use pgBackRest::Common::Exception;
|
||||
use pgBackRest::Common::Log;
|
||||
use pgBackRest::FileCommon;
|
||||
|
||||
use pgBackRestTest::Common::ExecuteTest;
|
||||
use pgBackRestTest::Common::RunTest;
|
||||
|
||||
####################################################################################################################################
|
||||
# run
|
||||
####################################################################################################################################
|
||||
sub run
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
################################################################################################################################
|
||||
if ($self->begin("FileCommon::fileLinkDestination()"))
|
||||
{
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
my $strTestLink = $self->testPath() . '/public_dir_link';
|
||||
|
||||
$self->testException(
|
||||
sub {fileLinkDestination($strTestLink)}, ERROR_FILE_MISSING,
|
||||
"unable to get destination for link ${strTestLink}: No such file or directory");
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
my $strTestPath = $self->testPath() . '/public_dir';
|
||||
filePathCreate($strTestPath);
|
||||
|
||||
$self->testException(
|
||||
sub {fileLinkDestination($strTestPath)}, ERROR_FILE_OPEN,
|
||||
"unable to get destination for link ${strTestPath}: Invalid argument");
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
symlink($strTestPath, $strTestLink)
|
||||
or confess &log(ERROR, "unable to create symlink from ${strTestPath} to ${strTestLink}");
|
||||
|
||||
$self->testResult(sub {fileLinkDestination($strTestLink)}, $strTestPath, 'get link destination');
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
@ -12,8 +12,13 @@ use warnings FATAL => qw(all);
|
||||
use Carp qw(confess);
|
||||
use English '-no_match_vars';
|
||||
|
||||
use File::Basename qw(basename dirname);
|
||||
use IO::Socket::UNIX;
|
||||
|
||||
use pgBackRest::Common::Log;
|
||||
use pgBackRest::Common::Exception;
|
||||
use pgBackRest::File;
|
||||
use pgBackRest::FileCommon;
|
||||
|
||||
use pgBackRestTest::Common::ExecuteTest;
|
||||
|
||||
@ -24,30 +29,124 @@ sub run
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
my $strManifestCompare =
|
||||
'.,d,' . $self->pgUser() . ',' . $self->group() . ",0770,,,,\n" .
|
||||
'sub1,d,' . $self->pgUser() . ',' . $self->group() . ",0750,,,,\n" .
|
||||
'sub1/sub2,d,' . $self->pgUser() . ',' . $self->group() . ",0750,,,,\n" .
|
||||
'sub1/sub2/test,l,' . $self->pgUser() . ',' . $self->group() . ",,,,,../..\n" .
|
||||
'sub1/sub2/test-hardlink.txt,f,' . $self->pgUser() . ',' . $self->group() . ",1640,1111111111,0,9,\n" .
|
||||
'sub1/sub2/test-sub2.txt,f,' . $self->pgUser() . ',' . $self->group() . ",0666,1111111113,0,11,\n" .
|
||||
'sub1/test,l,' . $self->pgUser() . ',' . $self->group() . ",,,,,..\n" .
|
||||
'sub1/test-hardlink.txt,f,' . $self->pgUser() . ',' . $self->group() . ",1640,1111111111,0,9,\n" .
|
||||
'sub1/test-sub1.txt,f,' . $self->pgUser() . ',' . $self->group() . ",0646,1111111112,0,10,\n" .
|
||||
'test.txt,f,' . $self->pgUser() . ',' . $self->group() . ",1640,1111111111,0,9,";
|
||||
################################################################################################################################
|
||||
if ($self->begin("FileCommon::fileManifestStat()"))
|
||||
{
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
my $strFile = $self->testPath() . '/test.txt';
|
||||
|
||||
$self->testResult(sub {pgBackRest::FileCommon::fileManifestStat($strFile)}, '[undef]', 'ignore missing file');
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
fileStringWrite($strFile, "TEST");
|
||||
utime(1111111111, 1111111111, $strFile);
|
||||
executeTest('chmod 1640 ' . $strFile);
|
||||
|
||||
$self->testResult(
|
||||
sub {pgBackRest::FileCommon::fileManifestStat($strFile)},
|
||||
'{group => ' . $self->group() .
|
||||
', mode => 1640, modification_time => 1111111111, size => 4, type => f, user => ' . $self->pgUser() . '}',
|
||||
'stat file');
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
my $strSocketFile = $self->testPath() . '/test.socket';
|
||||
|
||||
# Create a socket to test invalid files
|
||||
my $oSocket = IO::Socket::UNIX->new(Type => SOCK_STREAM(), Local => $strSocketFile, Listen => 1);
|
||||
|
||||
$self->testException(
|
||||
sub {pgBackRest::FileCommon::fileManifestStat($strSocketFile)}, ERROR_FILE_INVALID,
|
||||
"${strSocketFile} is not of type directory, file, or link");
|
||||
|
||||
# Cleanup socket
|
||||
$oSocket->close();
|
||||
fileRemove($strSocketFile);
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
my $strTestPath = $self->testPath() . '/public_dir';
|
||||
filePathCreate($strTestPath, '0750');
|
||||
|
||||
$self->testResult(
|
||||
sub {pgBackRest::FileCommon::fileManifestStat($strTestPath)},
|
||||
'{group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}',
|
||||
'stat directory');
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
my $strTestLink = $self->testPath() . '/public_dir_link';
|
||||
|
||||
symlink($strTestPath, $strTestLink)
|
||||
or confess &log(ERROR, "unable to create symlink from ${strTestPath} to ${strTestLink}");
|
||||
|
||||
$self->testResult(
|
||||
sub {pgBackRest::FileCommon::fileManifestStat($strTestLink)},
|
||||
'{group => ' . $self->group() . ", link_destination => ${strTestPath}, type => l, user => " . $self->pgUser() . '}',
|
||||
'stat link');
|
||||
}
|
||||
|
||||
################################################################################################################################
|
||||
if ($self->begin("FileCommon::fileManifestRecurse()"))
|
||||
{
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
my $strTestPath = $self->testPath() . '/public_dir';
|
||||
my $strTestFile = "${strTestPath}/test.txt";
|
||||
|
||||
$self->testException(
|
||||
sub {my $hManifest = {}; pgBackRest::FileCommon::fileManifestRecurse($strTestFile, undef, 0, $hManifest); $hManifest},
|
||||
ERROR_FILE_MISSING, "unable to stat ${strTestFile}: No such file or directory");
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
filePathCreate($strTestPath, '0750');
|
||||
|
||||
$self->testResult(
|
||||
sub {my $hManifest = {}; pgBackRest::FileCommon::fileManifestRecurse($strTestPath, undef, 0, $hManifest); $hManifest},
|
||||
'{. => {group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}}',
|
||||
'empty directory manifest');
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
fileStringWrite($strTestFile, "TEST");
|
||||
utime(1111111111, 1111111111, $strTestFile);
|
||||
executeTest('chmod 0750 ' . $strTestFile);
|
||||
|
||||
filePathCreate("${strTestPath}/sub", '0750');
|
||||
|
||||
$self->testResult(
|
||||
sub {my $hManifest = {}; pgBackRest::FileCommon::fileManifestRecurse(
|
||||
$self->testPath(), basename($strTestPath), 1, $hManifest); $hManifest},
|
||||
'{public_dir => {group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}, ' .
|
||||
'public_dir/sub => {group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}, ' .
|
||||
'public_dir/' . basename($strTestFile) . ' => {group => ' . $self->group() .
|
||||
', mode => 0750, modification_time => 1111111111, size => 4, type => f, user => ' . $self->pgUser() . '}}',
|
||||
'directory and file manifest');
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
$self->testResult(
|
||||
sub {my $hManifest = {}; pgBackRest::FileCommon::fileManifestRecurse($strTestFile, undef, 0, $hManifest); $hManifest},
|
||||
'{' . basename($strTestFile) . ' => {group => ' . $self->group() .
|
||||
', mode => 0750, modification_time => 1111111111, size => 4, type => f, user => ' . $self->pgUser() . '}}',
|
||||
'single file manifest');
|
||||
}
|
||||
|
||||
# Loop through local/remote
|
||||
for (my $bRemote = 0; $bRemote <= 1; $bRemote++)
|
||||
for (my $bRemote = false; $bRemote <= true; $bRemote++)
|
||||
{
|
||||
for (my $bError = 0; $bError <= 1; $bError++)
|
||||
{
|
||||
for (my $bExists = 0; $bExists <= 1; $bExists++)
|
||||
{
|
||||
if (!$self->begin("rmt ${bRemote}, exists ${bExists}, err ${bError}")) {next}
|
||||
if (!$self->begin('File->manifest() => ' . ($bRemote ? 'remote' : 'local'))) {next}
|
||||
|
||||
# Setup test directory and get file object
|
||||
my $oFile = $self->setup($bRemote, $bError);
|
||||
# Create the file object
|
||||
my $oFile = new pgBackRest::File
|
||||
(
|
||||
$self->stanza(),
|
||||
$self->testPath(),
|
||||
$bRemote ? $self->remote() : $self->local()
|
||||
);
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
my $strMissingFile = $self->testPath() . '/missing';
|
||||
|
||||
$self->testException(
|
||||
sub {$oFile->manifest(PATH_BACKUP_ABSOLUTE, $strMissingFile)},
|
||||
ERROR_FILE_MISSING, "unable to stat ${strMissingFile}: No such file or directory");
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
# Setup test data
|
||||
executeTest('mkdir -m 750 ' . $self->testPath() . '/sub1');
|
||||
executeTest('mkdir -m 750 ' . $self->testPath() . '/sub1/sub2');
|
||||
@ -74,88 +173,30 @@ sub run
|
||||
|
||||
executeTest('chmod 0770 ' . $self->testPath());
|
||||
|
||||
# Create path
|
||||
my $strPath = $self->testPath();
|
||||
|
||||
if ($bError)
|
||||
{
|
||||
$strPath = $self->testPath() . '/' . ($bRemote ? 'user' : 'backrest') . '_private';
|
||||
}
|
||||
elsif (!$bExists)
|
||||
{
|
||||
$strPath = $self->testPath() . '/error';
|
||||
}
|
||||
|
||||
# Execute in eval in case of error
|
||||
my $hManifest;
|
||||
my $bErrorExpected = !$bExists || $bError;
|
||||
|
||||
eval
|
||||
{
|
||||
$hManifest = $oFile->manifest(PATH_BACKUP_ABSOLUTE, $strPath);
|
||||
return true;
|
||||
}
|
||||
# Check for an error
|
||||
or do
|
||||
{
|
||||
if ($bErrorExpected)
|
||||
{
|
||||
next;
|
||||
}
|
||||
|
||||
confess $EVAL_ERROR;
|
||||
};
|
||||
|
||||
# Check for an expected error
|
||||
if ($bErrorExpected)
|
||||
{
|
||||
confess 'error was expected';
|
||||
}
|
||||
|
||||
my $strManifest;
|
||||
|
||||
# Validate the manifest
|
||||
foreach my $strName (sort(keys(%{$hManifest})))
|
||||
{
|
||||
if (!defined($strManifest))
|
||||
{
|
||||
$strManifest = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
$strManifest .= "\n";
|
||||
}
|
||||
|
||||
if (defined($hManifest->{$strName}{inode}))
|
||||
{
|
||||
$hManifest->{$strName}{inode} = 0;
|
||||
}
|
||||
|
||||
$strManifest .=
|
||||
"${strName}," .
|
||||
$hManifest->{$strName}{type} . ',' .
|
||||
(defined($hManifest->{$strName}{user}) ?
|
||||
$hManifest->{$strName}{user} : '') . ',' .
|
||||
(defined($hManifest->{$strName}{group}) ?
|
||||
$hManifest->{$strName}{group} : '') . ',' .
|
||||
(defined($hManifest->{$strName}{mode}) ?
|
||||
$hManifest->{$strName}{mode} : '') . ',' .
|
||||
(defined($hManifest->{$strName}{modification_time}) ?
|
||||
$hManifest->{$strName}{modification_time} : '') . ',' .
|
||||
(defined($hManifest->{$strName}{inode}) ?
|
||||
$hManifest->{$strName}{inode} : '') . ',' .
|
||||
(defined($hManifest->{$strName}{size}) ?
|
||||
$hManifest->{$strName}{size} : '') . ',' .
|
||||
(defined($hManifest->{$strName}{link_destination}) ?
|
||||
$hManifest->{$strName}{link_destination} : '');
|
||||
}
|
||||
|
||||
if ($strManifest ne $strManifestCompare)
|
||||
{
|
||||
confess "manifest is not equal:\n\n${strManifest}\n\ncompare:\n\n${strManifestCompare}\n\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
$self->testResult(
|
||||
sub {$oFile->manifest(PATH_BACKUP_ABSOLUTE, $self->testPath())},
|
||||
'{. => {group => ' . $self->group() . ', mode => 0770, type => d, user => ' . $self->pgUser() . '}, ' .
|
||||
'sub1 => {group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}, ' .
|
||||
'sub1/sub2 => {group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}, ' .
|
||||
'sub1/sub2/test => {group => ' . $self->group() . ', link_destination => ../.., type => l, user => ' .
|
||||
$self->pgUser() . '}, ' .
|
||||
'sub1/sub2/test-hardlink.txt => ' .
|
||||
'{group => ' . $self->group() . ', mode => 1640, modification_time => 1111111111, size => 9, type => f, user => ' .
|
||||
$self->pgUser() . '}, ' .
|
||||
'sub1/sub2/test-sub2.txt => ' .
|
||||
'{group => ' . $self->group() . ', mode => 0666, modification_time => 1111111113, size => 11, type => f, user => ' .
|
||||
$self->pgUser() . '}, ' .
|
||||
'sub1/test => {group => ' . $self->group() . ', link_destination => .., type => l, user => ' . $self->pgUser() . '}, ' .
|
||||
'sub1/test-hardlink.txt => ' .
|
||||
'{group => ' . $self->group() . ', mode => 1640, modification_time => 1111111111, size => 9, type => f, user => ' .
|
||||
$self->pgUser() . '}, ' .
|
||||
'sub1/test-sub1.txt => ' .
|
||||
'{group => ' . $self->group() . ', mode => 0646, modification_time => 1111111112, size => 10, type => f, user => ' .
|
||||
$self->pgUser() . '}, ' .
|
||||
'test.txt => ' .
|
||||
'{group => ' . $self->group() . ', mode => 1640, modification_time => 1111111111, size => 9, type => f, user => ' .
|
||||
$self->pgUser() . '}}',
|
||||
'complete manifest');
|
||||
}
|
||||
}
|
||||
|
||||
|
64
test/lib/pgBackRestTest/File/FileStatTest.pm
Normal file
64
test/lib/pgBackRestTest/File/FileStatTest.pm
Normal file
@ -0,0 +1,64 @@
|
||||
####################################################################################################################################
|
||||
# FileStatTest.pm - Tests for FileCommon::fileStat
|
||||
####################################################################################################################################
|
||||
package pgBackRestTest::File::FileStatTest;
|
||||
use parent 'pgBackRestTest::File::FileCommonTest';
|
||||
|
||||
####################################################################################################################################
|
||||
# Perl includes
|
||||
####################################################################################################################################
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
use Carp qw(confess);
|
||||
use English '-no_match_vars';
|
||||
|
||||
use Fcntl qw(:mode);
|
||||
use File::stat;
|
||||
|
||||
use pgBackRest::Common::Exception;
|
||||
use pgBackRest::Common::Log;
|
||||
use pgBackRest::FileCommon;
|
||||
|
||||
use pgBackRestTest::Common::ExecuteTest;
|
||||
use pgBackRestTest::Common::RunTest;
|
||||
|
||||
####################################################################################################################################
|
||||
# run
|
||||
####################################################################################################################################
|
||||
sub run
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
################################################################################################################################
|
||||
if ($self->begin("FileCommon::fileStat()"))
|
||||
{
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
my $strFile = $self->testPath() . '/test.txt';
|
||||
|
||||
$self->testException(sub {fileStat($strFile)}, ERROR_FILE_MISSING, "unable to stat ${strFile}: No such file or directory");
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
$self->testResult(sub {fileStat($strFile, true)}, '[undef]', 'ignore missing file');
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
fileStringWrite($strFile);
|
||||
|
||||
$self->testResult(sub {fileStat($strFile)}, '[object]', 'stat file');
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
$self->testResult(sub {S_ISREG(fileStat($strFile)->mode)}, true, 'check stat file result');
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
my $strPrivateDir = $self->testPath() . '/private_dir';
|
||||
my $strPrivateFile = "${strPrivateDir}/test.txt";
|
||||
executeTest("sudo mkdir -m 700 ${strPrivateDir}");
|
||||
|
||||
$self->testException(
|
||||
sub {fileStat($strPrivateFile, true)}, ERROR_FILE_OPEN, "unable to stat ${strPrivateFile}: Permission denied");
|
||||
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
$self->testResult(sub {S_ISDIR(fileStat($strPrivateDir)->mode)}, true, 'check stat directory result');
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
@ -696,8 +696,8 @@ sub run
|
||||
testLinkCreate("${strTblSpcPath}/99999", $oHostDbMaster->dbBasePath() . '_tbs');
|
||||
|
||||
$oHostBackup->backup(
|
||||
$strType, '$PGDATA is a substring of valid tblspc excluding / (file open err expected)',
|
||||
{oExpectedManifest => \%oManifest, iExpectedExitStatus => ERROR_FILE_OPEN,
|
||||
$strType, '$PGDATA is a substring of valid tblspc excluding / (file missing err expected)',
|
||||
{oExpectedManifest => \%oManifest, iExpectedExitStatus => ERROR_FILE_MISSING,
|
||||
strOptionalParam => '--log-level-console=detail'});
|
||||
|
||||
testFileRemove("${strTblSpcPath}/99999");
|
||||
|
Loading…
x
Reference in New Issue
Block a user