Просмотр исходного кода

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
Родитель
Сommit
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
     regenerating it. Closes: #606080
     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 ]
   * 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";
 
 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::Gettext;
@@ -64,6 +64,30 @@ sub fixperms {
     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($) {
     my ($file) = @_;
 

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

@@ -151,8 +151,7 @@ sub do_extract {
         my $patch = "$dscdir$difffile";
 	info(_g("applying %s"), $difffile);
 	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/} }
 		    sort keys %{$analysis->{'filepatched'}};
 	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::Patch;
 use Dpkg::Exit;
-use Dpkg::Source::Functions qw(erasedir is_binary);
+use Dpkg::Source::Functions qw(erasedir is_binary fs_time);
 
 use POSIX;
 use File::Basename;
@@ -206,9 +206,9 @@ sub apply_patches {
     $opts{"skip_auto"} = 0 unless defined($opts{"skip_auto"});
     my @patches = $self->get_patches($dir, %opts);
     return unless scalar(@patches);
-    my $timestamp = time();
     my $applied = File::Spec->catfile($dir, "debian", "patches", ".dpkg-source-applied");
     open(APPLIED, '>', $applied) || syserr(_g("cannot write %s"), $applied);
+    my $timestamp = fs_time($applied);
     foreach my $patch ($self->get_patches($dir, %opts)) {
         my $path = File::Spec->catfile($dir, "debian", "patches", $patch);
         info(_g("applying %s"), $patch) unless $opts{"skip_auto"};
@@ -225,8 +225,8 @@ sub unapply_patches {
     my ($self, $dir, %opts) = @_;
     my @patches = reverse($self->get_patches($dir, %opts));
     return unless scalar(@patches);
-    my $timestamp = time();
     my $applied = File::Spec->catfile($dir, "debian", "patches", ".dpkg-source-applied");
+    my $timestamp = fs_time($applied);
     foreach my $patch (@patches) {
         my $path = File::Spec->catfile($dir, "debian", "patches", $patch);
         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::ErrorHandling;
 use Dpkg::Source::Patch;
-use Dpkg::Source::Functions qw(erasedir);
+use Dpkg::Source::Functions qw(erasedir fs_time);
 use Dpkg::IPC;
 use Dpkg::Vendor qw(get_current_vendor run_vendor_hook);
 use Dpkg::Control;
@@ -164,7 +164,7 @@ sub create_quilt_db {
 sub apply_quilt_patch {
     my ($self, $dir, $patch, %opts) = @_;
     $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 $obj = Dpkg::Source::Patch->new(filename => $path);
 
@@ -221,7 +221,7 @@ sub apply_patches {
     my @applied = $self->read_patch_list($pc_applied);
     my @patches = $self->read_patch_list($self->get_series_file($dir));
     open(APPLIED, '>>', $pc_applied) || syserr(_g("cannot write %s"), $pc_applied);
-    $opts{"timestamp"} = time();
+    $opts{"timestamp"} = fs_time($pc_applied);
     foreach my $patch (@$patches) {
         $self->apply_quilt_patch($dir, $patch, %opts);
         print APPLIED "$patch\n";
@@ -236,7 +236,7 @@ sub unapply_patches {
 
     my $pc_applied = File::Spec->catfile($dir, ".pc", "applied-patches");
     my @applied = $self->read_patch_list($pc_applied);
-    $opts{"timestamp"} = time();
+    $opts{"timestamp"} = fs_time($pc_applied) if @applied;
     foreach my $patch (reverse @applied) {
         my $path = File::Spec->catfile($dir, "debian", "patches", $patch);
         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::IPC;
 use Dpkg::ErrorHandling;
+use Dpkg::Source::Functions qw(fs_time);
 
 use POSIX;
 use File::Find;
@@ -533,8 +534,10 @@ sub apply {
     $self->close();
     # Reset the timestamp of all the patched 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"}) {
 	    utime($now, $now, $fn) || $! == ENOENT ||
 		syserr(_g("cannot change timestamp for %s"), $fn);