Fix RecursiveCopy.pm to cope with disappearing files.

When copying from an active database tree, it's possible for files to be
deleted after we see them in a readdir() scan but before we can open them.
(Once we've got a file open, we don't expect any further errors from it
getting unlinked, though.)  Tweak RecursiveCopy so it can cope with this
case, so as to avoid irreproducible test failures.

Back-patch to 9.6 where this code was added.  In v10 and HEAD, also
remove unused "use RecursiveCopy" in one recovery test script.

Michael Paquier and Tom Lane

Discussion: https://postgr.es/m/24621.1504924323@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2017-09-11 22:02:58 -04:00
parent 57e1c00793
commit e183530550
2 changed files with 46 additions and 21 deletions

View File

@ -29,12 +29,17 @@ use File::Copy;
=head2 copypath($from, $to, %params)
Recursively copy all files and directories from $from to $to.
Does not preserve file metadata (e.g., permissions).
Only regular files and subdirectories are copied. Trying to copy other types
of directory entries raises an exception.
Raises an exception if a file would be overwritten, the source directory can't
be read, or any I/O operation fails. Always returns true.
be read, or any I/O operation fails. However, we silently ignore ENOENT on
open, because when copying from a live database it's possible for a file/dir
to be deleted after we see its directory entry but before we can open it.
Always returns true.
If the B<filterfn> parameter is given, it must be a subroutine reference.
This subroutine will be called for each entry in the source directory with its
@ -74,6 +79,9 @@ sub copypath
$filterfn = sub { return 1; };
}
# Complain if original path is bogus, because _copypath_recurse won't.
die "\"$base_src_dir\" does not exist" if !-e $base_src_dir;
# Start recursive copy from current directory
return _copypath_recurse($base_src_dir, $base_dest_dir, "", $filterfn);
}
@ -89,12 +97,8 @@ sub _copypath_recurse
return 1 unless &$filterfn($curr_path);
# Check for symlink -- needed only on source dir
die "Cannot operate on symlinks" if -l $srcpath;
# Can't handle symlinks or other weird things
die "Source path \"$srcpath\" is not a regular file or directory"
unless -f $srcpath
or -d $srcpath;
# (note: this will fall through quietly if file is already gone)
die "Cannot operate on symlink \"$srcpath\"" if -l $srcpath;
# Abort if destination path already exists. Should we allow directories
# to exist already?
@ -104,25 +108,47 @@ sub _copypath_recurse
# same name and we're done.
if (-f $srcpath)
{
copy($srcpath, $destpath)
my $fh;
unless (open($fh, '<', $srcpath))
{
return 1 if ($!{ENOENT});
die "open($srcpath) failed: $!";
}
copy($fh, $destpath)
or die "copy $srcpath -> $destpath failed: $!";
close $fh;
return 1;
}
# Otherwise this is directory: create it on dest and recurse onto it.
mkdir($destpath) or die "mkdir($destpath) failed: $!";
opendir(my $directory, $srcpath) or die "could not opendir($srcpath): $!";
while (my $entry = readdir($directory))
# If it's a directory, create it on dest and recurse into it.
if (-d $srcpath)
{
next if ($entry eq '.' or $entry eq '..');
_copypath_recurse($base_src_dir, $base_dest_dir,
$curr_path eq '' ? $entry : "$curr_path/$entry", $filterfn)
or die "copypath $srcpath/$entry -> $destpath/$entry failed";
}
closedir($directory);
my $directory;
unless (opendir($directory, $srcpath))
{
return 1 if ($!{ENOENT});
die "opendir($srcpath) failed: $!";
}
return 1;
mkdir($destpath) or die "mkdir($destpath) failed: $!";
while (my $entry = readdir($directory))
{
next if ($entry eq '.' or $entry eq '..');
_copypath_recurse($base_src_dir, $base_dest_dir,
$curr_path eq '' ? $entry : "$curr_path/$entry", $filterfn)
or die "copypath $srcpath/$entry -> $destpath/$entry failed";
}
closedir($directory);
return 1;
}
# If it disappeared from sight, that's OK.
return 1 if !-e $srcpath;
# Else it's some weird file type; complain.
die "Source path \"$srcpath\" is not a regular file or directory";
}
1;

View File

@ -24,7 +24,6 @@ use warnings;
use PostgresNode;
use TestLib;
use Test::More tests => 13;
use RecursiveCopy;
use File::Copy;
use IPC::Run ();
use Scalar::Util qw(blessed);