1
0
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:
David Steele 2017-02-13 19:59:14 -05:00
parent 845c6112bf
commit e2ac7e1ea6
12 changed files with 445 additions and 220 deletions

View File

@ -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&amp;uuml;nd&amp;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>

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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};

View File

@ -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

View File

@ -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,
},
{

View File

@ -55,6 +55,9 @@ sub initModule
$self->host(),
$self->backrestUser(),
HOST_PROTOCOL_TIMEOUT);
rmdir($strRepoPath)
or confess "Unable to remove repo directory: ${strRepoPath}";
}
####################################################################################################################################

View 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;

View File

@ -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');
}
}

View 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;

View File

@ -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");