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) =head2 copypath($from, $to, %params)
Recursively copy all files and directories from $from to $to. 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 Only regular files and subdirectories are copied. Trying to copy other types
of directory entries raises an exception. of directory entries raises an exception.
Raises an exception if a file would be overwritten, the source directory can't 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. 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 This subroutine will be called for each entry in the source directory with its
@ -74,6 +79,9 @@ sub copypath
$filterfn = sub { return 1; }; $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 # Start recursive copy from current directory
return _copypath_recurse($base_src_dir, $base_dest_dir, "", $filterfn); return _copypath_recurse($base_src_dir, $base_dest_dir, "", $filterfn);
} }
@ -89,12 +97,8 @@ sub _copypath_recurse
return 1 unless &$filterfn($curr_path); return 1 unless &$filterfn($curr_path);
# Check for symlink -- needed only on source dir # Check for symlink -- needed only on source dir
die "Cannot operate on symlinks" if -l $srcpath; # (note: this will fall through quietly if file is already gone)
die "Cannot operate on symlink \"$srcpath\"" 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;
# Abort if destination path already exists. Should we allow directories # Abort if destination path already exists. Should we allow directories
# to exist already? # to exist already?
@ -104,25 +108,47 @@ sub _copypath_recurse
# same name and we're done. # same name and we're done.
if (-f $srcpath) 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: $!"; or die "copy $srcpath -> $destpath failed: $!";
close $fh;
return 1; return 1;
} }
# Otherwise this is directory: create it on dest and recurse onto it. # If it's a directory, create it on dest and recurse into it.
mkdir($destpath) or die "mkdir($destpath) failed: $!"; if (-d $srcpath)
opendir(my $directory, $srcpath) or die "could not opendir($srcpath): $!";
while (my $entry = readdir($directory))
{ {
next if ($entry eq '.' or $entry eq '..'); my $directory;
_copypath_recurse($base_src_dir, $base_dest_dir, unless (opendir($directory, $srcpath))
$curr_path eq '' ? $entry : "$curr_path/$entry", $filterfn) {
or die "copypath $srcpath/$entry -> $destpath/$entry failed"; return 1 if ($!{ENOENT});
} die "opendir($srcpath) failed: $!";
closedir($directory); }
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; 1;

View File

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