aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2020-01-23 09:11:15 +0200
committerSergey Poznyakoff <gray@gnu.org>2020-01-23 09:11:15 +0200
commit152ee1b1e753a6879989e27c0b56a4681e72aa8c (patch)
treed7ca6ab8e49e627d40a1dacc1b16bc82c3a24858
parentf3d76da04b7e918c104693b8584c4f4bd18a7c8d (diff)
downloadfile-backup-152ee1b1e753a6879989e27c0b56a4681e72aa8c.tar.gz
file-backup-152ee1b1e753a6879989e27c0b56a4681e72aa8c.tar.bz2
Improve portability.
* Makefile.PL: Require Win32::FileOp on MSWin32 * lib/File/BackupCopy.pm: Provide three different versions of atimical numbered backup creation for various OSes
-rw-r--r--Makefile.PL11
-rw-r--r--lib/File/BackupCopy.pm104
2 files changed, 104 insertions, 11 deletions
diff --git a/Makefile.PL b/Makefile.PL
index 1947389..ef3bb16 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -3,7 +3,7 @@ use warnings;
use ExtUtils::MakeMaker;
use Module::Metadata;
-WriteMakefile(
+my %makefile_args = (
NAME => 'File::BackupCopy',
VERSION_FROM => 'lib/File/BackupCopy.pm',
ABSTRACT_FROM => 'lib/File/BackupCopy.pm',
@@ -30,5 +30,12 @@ WriteMakefile(
}
);
-
+unless (eval { symlink("",""); 1 }) {
+ if ($^O eq 'MSWin32') {
+ $makefile_args{PREREQ_PM}{'Win32::FileOp'} = 0.16;
+ }
+}
+
+WriteMakefile(%makefile_args);
+
diff --git a/lib/File/BackupCopy.pm b/lib/File/BackupCopy.pm
index fd52029..87d198e 100644
--- a/lib/File/BackupCopy.pm
+++ b/lib/File/BackupCopy.pm
@@ -10,7 +10,7 @@ use re '/aa';
use Carp;
use Errno;
-our $VERSION = '1.00';
+our $VERSION = '1.00_02';
our @ISA = qw(Exporter);
our @EXPORT = qw(BACKUP_NONE
BACKUP_SINGLE
@@ -137,24 +137,110 @@ sub backup_copy_internal {
copy($file_name, $fh)
or return _backup_copy_error($error,
- "failed to make a temporary copy of $file_name: $!");
+ "failed to make a temporary copy of $file_name: $!");
+ my $backup_name = rename_backup($fh->filename, $backup_stub, $num, $error);
+ $fh->unlink_on_destroy(0) if $backup_name;
+ return $backup_name;
+}
+
+# The rename_backup function performs the final stage of numbered backup
+# creation: atomical rename of the temporary backup file to the actual
+# backup name.
+# The calling sequence is:
+# rename_backup($tempfile, $backup_stub, $num, $error)
+# where $tempfile is the name of the temporary file holding the backup,
+# $backup_stub is the name of the backup file without the actual
+# numbered suffix (may contain directory components,
+# if required).
+# $num is the first unused backup number,
+# $error is the reference to error message storage or undef.
+# The function creates the new backup file name from $backup_stub and
+# $num and attempts to rename $tempfile to it. If the rename failed
+# because such file already exists (i.e. another process created it in
+# between), the function increases the $num and retries. The process
+# continues until the rename succeeds or a fatal error is encountered,
+# whichever occurs first.
+#
+# Three versions of the function are provided. The right one to use
+# is selected when the module is loaded:
+
+BEGIN {
+ if (eval { symlink("",""); 1 }) {
+ *{rename_backup} = \&rename_backup_posix;
+ } elsif ($^O eq 'MSWin32' && eval { require Win32::FileOp }) {
+ *{rename_backup} = \&rename_backup_win32;
+ } else {
+ warn "using last resort rename method susceptible to a race condition";
+ *{rename_backup} = \&rename_backup_last_resort;
+ }
+}
+
+# rename_backup_posix - rename_backup for POSIX systems.
+# -------------------
+# In order to ensure atomic rename, the temporary file is first
+# symlinked to the desired backup name. This will fail if the
+# name already exists, in which case the function will try next
+# backup number. Once the symlink is created, temporary file
+# is renamed to it. This operation will silently destroy the
+# symlink and replace it with the backup file.
+sub rename_backup_posix {
+ my ($tempfilename, $backup_stub, $num, $error) = @_;
my $backup_name;
while (1) {
$backup_name = "$backup_stub.~$num~";
- last if symlink($fh->filename, $backup_name);
+ last if symlink($tempfilename, $backup_name);
unless ($!{EEXIST}) {
- return _backup_copy_error("can't link "
- . $fh->filename .
- " to $backup_name: $!");
+ return _backup_copy_error($error,
+ "can't link $tempfilename to $backup_name: $!");
}
++$num;
}
- unless (rename($fh->filename, $backup_name)) {
- return _backup_copy_error("can't rename temporary file to $backup_name: $!");
+ unless (rename($tempfilename, $backup_name)) {
+ return _backup_copy_error($error,
+ "can't rename temporary file to $backup_name: $!");
+ }
+ return $backup_name;
+}
+
+# rename_backup_win32 - rename_backup for MSWin32 systems with Win32::FileOp
+# -------------------
+# This function is used if Win32::FileOp was loaded successfully. It uses
+# the MoveFile function to ensure atomic renames.
+sub rename_backup_win32 {
+ my ($tempfilename, $backup_stub, $num, $error) = @_;
+ my $backup_name;
+ while (1) {
+ $backup_name = "$backup_stub.~$num~";
+ last if MoveFile($tempfilename, $backup_name);
+ unless ($!{EEXIST}) {
+ return _backup_copy_error($error,
+ "can't rename $tempfilename to $backup_name: $!");
+ }
+ ++$num;
+ }
+ return $backup_name;
+}
+
+# rename_backup_last_resort - a weaker version for the rest of systems
+# -------------------------
+# It is enabled on systems not offering the symlink function (except where
+# Win32::FileOp can be used). This version uses a combination of -f test
+# and rename. It suffers from an obvious race condition which occurs in
+# the time window between these.
+sub rename_backup_last_resort {
+ my ($tempfilename, $backup_stub, $num, $error) = @_;
+ my $backup_name;
+ while (1) {
+ $backup_name = "$backup_stub.~$num~";
+ unless (-f $backup_name) {
+ last if rename($tempfilename, $backup_name);
+ return _backup_copy_error($error,
+ "can't rename temporary file to $backup_name: $!");
+ }
+ ++$num;
}
- $fh->unlink_on_destroy(0);
return $backup_name;
}

Return to:

Send suggestions and report system problems to the System administrator.