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 <glo...@debian.org> Signed-off-by: Jonathan Nieder <jrnie...@gmail.com> Improved-by: Raphaël Hertzog <hert...@debian.org> --- Raphael Hertzog wrote: > You should also update the code for the source package formats > because both 1.0 and 3.0 (quilt) pass an explicit value > for the timestamp. Good call (and likewise for the other suggestions). Especially: > my ($first_file) = grep { -e $_ } @files; This is nice and idiomatic --- thanks! > I'm not sure whether stat or lstat is more appropriate. Probably lstat, but changed to stat for simplicity. scripts/Dpkg/Source/Functions.pm | 21 ++++++++++++++++++++- scripts/Dpkg/Source/Package/V1.pm | 2 +- scripts/Dpkg/Source/Package/V2.pm | 6 +++--- scripts/Dpkg/Source/Package/V3/quilt.pm | 8 ++++---- scripts/Dpkg/Source/Patch.pm | 13 +++++++++++-- 5 files changed, 39 insertions(+), 11 deletions(-) diff --git a/scripts/Dpkg/Source/Functions.pm b/scripts/Dpkg/Source/Functions.pm index 8588ed6..57c973c 100644 --- a/scripts/Dpkg/Source/Functions.pm +++ b/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,25 @@ sub fixperms { subprocerr("chmod -R -- $modes_set $dir") if $?; } +# Touch the first file that exists from @files and read the +# resulting mtime. +# +# 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 (@files) = @_; + my ($first_file) = grep { -e $_ } @files; + unless ($first_file) { + return time(); + } + utime(undef, undef, $first_file) || + syserr(_g("cannot change timestamp for %s"), $first_file); + stat($first_file) || + syserr(_g("cannot read timestamp from %s"), $first_file); + return (stat(_))[9]; +} + sub is_binary($) { my ($file) = @_; diff --git a/scripts/Dpkg/Source/Package/V1.pm b/scripts/Dpkg/Source/Package/V1.pm index 41023ca..38cbc17 100644 --- a/scripts/Dpkg/Source/Package/V1.pm +++ b/scripts/Dpkg/Source/Package/V1.pm @@ -152,7 +152,7 @@ sub do_extract { 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()); + timestamp => undef); my @files = grep { ! m{^\Q$newdirectory\E/debian/} } sort keys %{$analysis->{'filepatched'}}; info(_g("upstream files that have been modified: %s"), diff --git a/scripts/Dpkg/Source/Package/V2.pm b/scripts/Dpkg/Source/Package/V2.pm index 4eaad93..b1cd42e 100644 --- a/scripts/Dpkg/Source/Package/V2.pm +++ b/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, $dir) if @patches; foreach my $patch (@patches) { my $path = File::Spec->catfile($dir, "debian", "patches", $patch); info(_g("unapplying %s"), $patch) unless $opts{"quiet"}; diff --git a/scripts/Dpkg/Source/Package/V3/quilt.pm b/scripts/Dpkg/Source/Package/V3/quilt.pm index 1f2c8a0..e37da7a 100644 --- a/scripts/Dpkg/Source/Package/V3/quilt.pm +++ b/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); diff --git a/scripts/Dpkg/Source/Patch.pm b/scripts/Dpkg/Source/Patch.pm index 59d1004..b82cb1d 100644 --- a/scripts/Dpkg/Source/Patch.pm +++ b/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; @@ -501,8 +502,16 @@ 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'}}) { + # + # With NFS, the filesystem (server) and local clock (client) can + # disagree about the time. Everything else (normal modification, + # "touch") sets mtime using the time on the server side, so to + # avoid problems in the presence of time skew, we should, too. + # + my @files = keys %{$analysis->{'filepatched'}}; + my $now = $opts{"timestamp"}; + $now ||= fs_time(@files) 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); -- 1.7.4.1 -- To UNSUBSCRIBE, email to debian-bugs-dist-requ...@lists.debian.org with a subject of "unsubscribe". Trouble? Contact listmas...@lists.debian.org