chiark / gitweb /
Dgit.pm: rename_link_xf: Avoid copying if src is a link to dst
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Wed, 10 Oct 2018 23:43:45 +0000 (00:43 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Wed, 10 Oct 2018 23:58:27 +0000 (00:58 +0100)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
Debian/Dgit.pm

index 19ea85b..5b1feff 100644 (file)
@@ -434,6 +434,8 @@ sub rename_link_xf ($$$) {
     #    $@ to a reason message
     #    $! to an errno value, or -1 if not known
     # having possibly printed something about mv to stderr.
+    # Not safe to use without $keeporig if $dst might be a symlink
+    # to $src, as it might delete $src leaving $dst invalid.
     my ($keeporig,$src,$dst) = @_;
     if ($keeporig
        ? link   $src, $dst
@@ -444,19 +446,40 @@ sub rename_link_xf ($$$) {
        $@ = "$!";
        return 0;
     }
-    $!=0; $?=0;
-    my @cmd = (qw(cp --), $src, "$dst.tmp");
-    debugcmd '+',@cmd;
-    if (system @cmd) {
-       failedcmd_report_cmd undef, @cmd;
-       $@ = failedcmd_waitstatus();
-       $! = -1;
+    if (!stat $src) {
+       $@ = f_ "stat source file: %S", $!;
        return 0;
     }
-    if (!rename "$dst.tmp", $dst) {
-       $@ = f_ "finally install file after cp: %S", $!;
+    my @src_stat = (stat _)[0..1];
+
+    my @dst_stat;
+    if (stat $dst) {
+       @dst_stat = (stat _)[0..1];
+    } elsif ($! == ENOENT) {
+    } else {
+       $@ = f_ "stat destination file: %S", $!;
        return 0;
     }
+
+    if ("@src_stat" eq "@dst_stat") {
+       # (Symlinks to) the same file.  No need for a copy but
+       # we may need to delete the original.
+       printdebug "rename_link_xf $keeporig $src $dst EXDEV but same\n";
+    } else {
+       $!=0; $?=0;
+       my @cmd = (qw(cp --), $src, "$dst.tmp");
+       debugcmd '+',@cmd;
+       if (system @cmd) {
+           failedcmd_report_cmd undef, @cmd;
+           $@ = failedcmd_waitstatus();
+           $! = -1;
+           return 0;
+       }
+       if (!rename "$dst.tmp", $dst) {
+           $@ = f_ "finally install file after cp: %S", $!;
+           return 0;
+       }
+    }
     if (!$keeporig) {
        if (!unlink $src) {
            $@ = f_ "delete old file after cp: %S", $!;