Przeglądaj źródła

dpkg-source: use server-side clock for patched file timestamp on NFS

Since 1.13.14~20 (2006-02-10), dpkg-source touches the files it
patches when unpacking, with a single date.  This way, the order of
mtimes does not depend on the order in which the files were patched,
which is convenient when e.g. configure.in and configure are patched.

More precisely, dpkg-source does something like the following:

	my $now = time();
	foreach my $fn (@patched_files) {
		utime($now, $now, $fn);
	}

Unfortunately when the filesystem is NFS, "touch" and normal
modification set mtime and atime to the current time on the server
side, while time() returns the current time on the client side.
The two clocks can disagree, producing breakage.

So unless a timestamp has been passed explicitly, use utime(undef,
undef, $fn) to set mtime for the first file to the server side time
and copy it (rounded down to a number of seconds) to all patched
files.

Reported-by: Stéphane Glondu <glondu@debian.org>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Improved-by: Raphaël Hertzog <hertzog@debian.org>
Signed-off-by: Raphaël Hertzog <hertzog@debian.org>
Jonathan Nieder 15 lat temu
rodzic
commit
255e73e11d

+ 5 - 0
debian/changelog

@@ -68,6 +68,11 @@ dpkg (1.16.0) UNRELEASED; urgency=low
   * dpkg-source now keeps the file ordering in the autogenerated patch when
   * dpkg-source now keeps the file ordering in the autogenerated patch when
     regenerating it. Closes: #606080
     regenerating it. Closes: #606080
     Thanks to Colin Watson for the patch.
     Thanks to Colin Watson for the patch.
+  * dpkg-source now uses a timestamp retrieved from the filesystem when
+    resetting the timestamp of patched files so that a time skew when using
+    NFS doesn't introduce any inconsistency. Closes: #613023
+    Thanks to Jonathan Nieder <jrnieder@gmail.com> for the patch and the
+    diagnosis.
 
 
   [ Jonathan Nieder ]
   [ Jonathan Nieder ]
   * Remove support for use of synchronous sync(2), due to its pernicious
   * Remove support for use of synchronous sync(2), due to its pernicious

+ 25 - 1
scripts/Dpkg/Source/Functions.pm

@@ -19,7 +19,7 @@ use warnings;
 our $VERSION = "0.01";
 our $VERSION = "0.01";
 
 
 use base qw(Exporter);
 use base qw(Exporter);
-our @EXPORT_OK = qw(erasedir fixperms is_binary);
+our @EXPORT_OK = qw(erasedir fixperms fs_time is_binary);
 
 
 use Dpkg::ErrorHandling;
 use Dpkg::ErrorHandling;
 use Dpkg::Gettext;
 use Dpkg::Gettext;
@@ -64,6 +64,30 @@ sub fixperms {
     subprocerr("chmod -R -- $modes_set $dir") if $?;
     subprocerr("chmod -R -- $modes_set $dir") if $?;
 }
 }
 
 
+# Touch the file and read the resulting mtime.
+#
+# If the file doesn't exist, create it, read the mtime and unlink it.
+#
+# Use this instead of time() when the timestamp is going to be
+# used to set file timestamps. This avoids confusion when an
+# NFS server and NFS client disagree about what time it is.
+sub fs_time($) {
+    my ($file) = @_;
+    my $is_temp = 0;
+    if (not -e $file) {
+	open(TEMP, ">", $file) or syserr(_g("cannot write %s"));
+	close(TEMP);
+	$is_temp = 1;
+    } else {
+	utime(undef, undef, $file) or
+	    syserr(_g("cannot change timestamp for %s"), $file);
+    }
+    stat($file) or syserr(_g("cannot read timestamp from %s"), $file);
+    my $mtime = (stat(_))[9];
+    unlink($file) if $is_temp;
+    return $mtime;
+}
+
 sub is_binary($) {
 sub is_binary($) {
     my ($file) = @_;
     my ($file) = @_;
 
 

+ 1 - 2
scripts/Dpkg/Source/Package/V1.pm

@@ -151,8 +151,7 @@ sub do_extract {
         my $patch = "$dscdir$difffile";
         my $patch = "$dscdir$difffile";
 	info(_g("applying %s"), $difffile);
 	info(_g("applying %s"), $difffile);
 	my $patch_obj = Dpkg::Source::Patch->new(filename => $patch);
 	my $patch_obj = Dpkg::Source::Patch->new(filename => $patch);
-	my $analysis = $patch_obj->apply($newdirectory, force_timestamp => 1,
-                                         timestamp => time());
+	my $analysis = $patch_obj->apply($newdirectory, force_timestamp => 1);
 	my @files = grep { ! m{^\Q$newdirectory\E/debian/} }
 	my @files = grep { ! m{^\Q$newdirectory\E/debian/} }
 		    sort keys %{$analysis->{'filepatched'}};
 		    sort keys %{$analysis->{'filepatched'}};
 	info(_g("upstream files that have been modified: %s"),
 	info(_g("upstream files that have been modified: %s"),

+ 3 - 3
scripts/Dpkg/Source/Package/V2.pm

@@ -29,7 +29,7 @@ use Dpkg::Compression;
 use Dpkg::Source::Archive;
 use Dpkg::Source::Archive;
 use Dpkg::Source::Patch;
 use Dpkg::Source::Patch;
 use Dpkg::Exit;
 use Dpkg::Exit;
-use Dpkg::Source::Functions qw(erasedir is_binary);
+use Dpkg::Source::Functions qw(erasedir is_binary fs_time);
 
 
 use POSIX;
 use POSIX;
 use File::Basename;
 use File::Basename;
@@ -206,9 +206,9 @@ sub apply_patches {
     $opts{"skip_auto"} = 0 unless defined($opts{"skip_auto"});
     $opts{"skip_auto"} = 0 unless defined($opts{"skip_auto"});
     my @patches = $self->get_patches($dir, %opts);
     my @patches = $self->get_patches($dir, %opts);
     return unless scalar(@patches);
     return unless scalar(@patches);
-    my $timestamp = time();
     my $applied = File::Spec->catfile($dir, "debian", "patches", ".dpkg-source-applied");
     my $applied = File::Spec->catfile($dir, "debian", "patches", ".dpkg-source-applied");
     open(APPLIED, '>', $applied) || syserr(_g("cannot write %s"), $applied);
     open(APPLIED, '>', $applied) || syserr(_g("cannot write %s"), $applied);
+    my $timestamp = fs_time($applied);
     foreach my $patch ($self->get_patches($dir, %opts)) {
     foreach my $patch ($self->get_patches($dir, %opts)) {
         my $path = File::Spec->catfile($dir, "debian", "patches", $patch);
         my $path = File::Spec->catfile($dir, "debian", "patches", $patch);
         info(_g("applying %s"), $patch) unless $opts{"skip_auto"};
         info(_g("applying %s"), $patch) unless $opts{"skip_auto"};
@@ -225,8 +225,8 @@ sub unapply_patches {
     my ($self, $dir, %opts) = @_;
     my ($self, $dir, %opts) = @_;
     my @patches = reverse($self->get_patches($dir, %opts));
     my @patches = reverse($self->get_patches($dir, %opts));
     return unless scalar(@patches);
     return unless scalar(@patches);
-    my $timestamp = time();
     my $applied = File::Spec->catfile($dir, "debian", "patches", ".dpkg-source-applied");
     my $applied = File::Spec->catfile($dir, "debian", "patches", ".dpkg-source-applied");
+    my $timestamp = fs_time($applied);
     foreach my $patch (@patches) {
     foreach my $patch (@patches) {
         my $path = File::Spec->catfile($dir, "debian", "patches", $patch);
         my $path = File::Spec->catfile($dir, "debian", "patches", $patch);
         info(_g("unapplying %s"), $patch) unless $opts{"quiet"};
         info(_g("unapplying %s"), $patch) unless $opts{"quiet"};

+ 4 - 4
scripts/Dpkg/Source/Package/V3/quilt.pm

@@ -27,7 +27,7 @@ use Dpkg;
 use Dpkg::Gettext;
 use Dpkg::Gettext;
 use Dpkg::ErrorHandling;
 use Dpkg::ErrorHandling;
 use Dpkg::Source::Patch;
 use Dpkg::Source::Patch;
-use Dpkg::Source::Functions qw(erasedir);
+use Dpkg::Source::Functions qw(erasedir fs_time);
 use Dpkg::IPC;
 use Dpkg::IPC;
 use Dpkg::Vendor qw(get_current_vendor run_vendor_hook);
 use Dpkg::Vendor qw(get_current_vendor run_vendor_hook);
 use Dpkg::Control;
 use Dpkg::Control;
@@ -164,7 +164,7 @@ sub create_quilt_db {
 sub apply_quilt_patch {
 sub apply_quilt_patch {
     my ($self, $dir, $patch, %opts) = @_;
     my ($self, $dir, $patch, %opts) = @_;
     $opts{"verbose"} = 0 unless defined($opts{"verbose"});
     $opts{"verbose"} = 0 unless defined($opts{"verbose"});
-    $opts{"timestamp"} = time() unless defined($opts{"timestamp"});
+    $opts{"timestamp"} = fs_time($dir) unless defined($opts{"timestamp"});
     my $path = File::Spec->catfile($dir, "debian", "patches", $patch);
     my $path = File::Spec->catfile($dir, "debian", "patches", $patch);
     my $obj = Dpkg::Source::Patch->new(filename => $path);
     my $obj = Dpkg::Source::Patch->new(filename => $path);
 
 
@@ -221,7 +221,7 @@ sub apply_patches {
     my @applied = $self->read_patch_list($pc_applied);
     my @applied = $self->read_patch_list($pc_applied);
     my @patches = $self->read_patch_list($self->get_series_file($dir));
     my @patches = $self->read_patch_list($self->get_series_file($dir));
     open(APPLIED, '>>', $pc_applied) || syserr(_g("cannot write %s"), $pc_applied);
     open(APPLIED, '>>', $pc_applied) || syserr(_g("cannot write %s"), $pc_applied);
-    $opts{"timestamp"} = time();
+    $opts{"timestamp"} = fs_time($pc_applied);
     foreach my $patch (@$patches) {
     foreach my $patch (@$patches) {
         $self->apply_quilt_patch($dir, $patch, %opts);
         $self->apply_quilt_patch($dir, $patch, %opts);
         print APPLIED "$patch\n";
         print APPLIED "$patch\n";
@@ -236,7 +236,7 @@ sub unapply_patches {
 
 
     my $pc_applied = File::Spec->catfile($dir, ".pc", "applied-patches");
     my $pc_applied = File::Spec->catfile($dir, ".pc", "applied-patches");
     my @applied = $self->read_patch_list($pc_applied);
     my @applied = $self->read_patch_list($pc_applied);
-    $opts{"timestamp"} = time();
+    $opts{"timestamp"} = fs_time($pc_applied) if @applied;
     foreach my $patch (reverse @applied) {
     foreach my $patch (reverse @applied) {
         my $path = File::Spec->catfile($dir, "debian", "patches", $patch);
         my $path = File::Spec->catfile($dir, "debian", "patches", $patch);
         my $obj = Dpkg::Source::Patch->new(filename => $path);
         my $obj = Dpkg::Source::Patch->new(filename => $path);

+ 5 - 2
scripts/Dpkg/Source/Patch.pm

@@ -24,6 +24,7 @@ use Dpkg;
 use Dpkg::Gettext;
 use Dpkg::Gettext;
 use Dpkg::IPC;
 use Dpkg::IPC;
 use Dpkg::ErrorHandling;
 use Dpkg::ErrorHandling;
+use Dpkg::Source::Functions qw(fs_time);
 
 
 use POSIX;
 use POSIX;
 use File::Find;
 use File::Find;
@@ -533,8 +534,10 @@ sub apply {
     $self->close();
     $self->close();
     # Reset the timestamp of all the patched files
     # Reset the timestamp of all the patched files
     # and remove .dpkg-orig files
     # and remove .dpkg-orig files
-    my $now = $opts{"timestamp"} || time;
-    foreach my $fn (keys %{$analysis->{'filepatched'}}) {
+    my @files = keys %{$analysis->{'filepatched'}};
+    my $now = $opts{"timestamp"};
+    $now ||= fs_time($files[0]) if $opts{"force_timestamp"};
+    foreach my $fn (@files) {
 	if ($opts{"force_timestamp"}) {
 	if ($opts{"force_timestamp"}) {
 	    utime($now, $now, $fn) || $! == ENOENT ||
 	    utime($now, $now, $fn) || $! == ENOENT ||
 		syserr(_g("cannot change timestamp for %s"), $fn);
 		syserr(_g("cannot change timestamp for %s"), $fn);