Jérémy Bobbio:
> Guillem Jover:
> > The timestamp on the ar member let's you know when the package got
> > built.
> 
> I don't believe this add much value: we have this information in the
> .changes files already.
> 
> Anyway, I was thinking of adding a `--timestamp=1377619307` option to
> `dpkg-deb`. The value would default to `time(NULL)` when the flag is
> missing. This could allow the rebuild tool to extract the timestamp from
> the .changes file in order to match the initial build. But this has
> extra complications given that different calls to dpkg-deb and
> dpkg-genchange will have different timestamps at the moment.

I have a test implementation of that idea ready. See the attached
patches (extracted from the `pu/reproducible_builds` branch available
from `git://anonscm.debian.org/users/lunar/dpkg.git`).

This makes the following work:

$ apt-get source hello
$ cd hello-2.8
$ dpkg-buildpackage
[…]
$ cp ../hello_2.8-4_amd64.deb ../hello_2.8-4_amd64.deb.orig
$ DEB_BUILD_TIMESTAMP=$(date +%s -d"$(sed -n -e 's/^Date: //p' 
../hello_2.8-4_amd64.changes)") dpkg-buildpackage
[…]
$ sha256sum ../hello_2.8-4_amd64.deb ../hello_2.8-4_amd64.deb.orig
1e944abfceac7e593f6706da971e0444e5cee9aab680de5292d52661940ee9c4 
../hello_2.8-4_amd64.deb
1e944abfceac7e593f6706da971e0444e5cee9aab680de5292d52661940ee9c4 
../hello_2.8-4_amd64.deb.orig

You probably will have several comments regarding the implementation,
but I was wondering how you felt with the overall approach.

-- 
Lunar                                .''`. 
lu...@debian.org                    : :Ⓐ  :  # apt-get install anarchism
                                    `. `'` 
                                      `-   
From 0275d0a656de921693f22c4b3dbd780670698829 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Bobbio?= <lu...@debian.org>
Date: Tue, 27 Aug 2013 22:38:31 +0200
Subject: [PATCH 1/3] Use a single timestamp when building a .deb

In order to make build reproducible in the future, we now use a single
timestamp in all ar headers and for all members of the control.tar and data.tar
archives.

Previously, each ar header would have the current time of its creation.
This level of precision is not really needed and the time of the beginning of
the build is good enough.

The tar members were previously using the mtime of the files on the filesystem.
In almost all cases, the files are created/copied during the package build, so
again, using the time of the creation of the .deb does not make much of a
difference.
---
 dpkg-deb/build.c   |   40 +++++++++++++++++++++++++++-------------
 dpkg-split/split.c |    4 ++--
 lib/dpkg/ar.c      |   12 ++++++------
 lib/dpkg/ar.h      |    6 +++---
 4 files changed, 38 insertions(+), 24 deletions(-)

diff --git a/dpkg-deb/build.c b/dpkg-deb/build.c
index 99b014d..73e5bdd 100644
--- a/dpkg-deb/build.c
+++ b/dpkg-deb/build.c
@@ -38,6 +38,7 @@
 #include <stdint.h>
 #include <stdlib.h>
 #include <stdio.h>
+#include <time.h>
 
 #include <dpkg/i18n.h>
 #include <dpkg/dpkg.h>
@@ -382,8 +383,9 @@ pkg_get_pathname(const char *dir, struct pkginfo *pkg)
   return path;
 }
 
+#define MTIME_ARG_LENGTH 30 /* length of --mtime=@18446744073709551616 + \0 */
 static void
-create_control_tar(const char *dir, int gzfd)
+create_control_tar(const char *dir, int gzfd, time_t mtime)
 {
   int p1[2], p2[2], p3[2];
   pid_t c1, c2, c3, c4;
@@ -394,13 +396,16 @@ create_control_tar(const char *dir, int gzfd)
   m_pipe(p2);
   c1 = subproc_fork();
   if (!c1) {
+    char mtime_arg[MTIME_ARG_LENGTH];
+
+    snprintf(mtime_arg, MTIME_ARG_LENGTH, "--mtime=@%lu", mtime);
     m_dup2(p1[0], 0); close(p1[0]); close(p1[1]);
     m_dup2(p2[1], 1); close(p2[0]); close(p2[1]);
     if (chdir(dir))
       ohshite(_("failed to chdir to `%.255s'"), dir);
     if (chdir(BUILDCONTROLDIR))
       ohshite(_("failed to chdir to `%.255s'"), ".../DEBIAN");
-    execlp(TAR, "tar", "-cf", "-", "--format=gnu", "--null", "-T", "-", "--no-recursion", NULL);
+    execlp(TAR, "tar", "-cf", "-", "--format=gnu", "--null", mtime_arg, "-T", "-", "--no-recursion", NULL);
     ohshite(_("unable to execute %s (%s)"), "tar -cf", TAR);
   }
   close(p1[0]);
@@ -456,6 +461,7 @@ create_control_tar(const char *dir, int gzfd)
   subproc_wait_check(c2, "gzip -9c", 0);
   subproc_wait_check(c1, "tar -cf", 0);
 }
+#undef MTIME_ARG_LENGTH
 
 static void
 write_format_zero_deb_header(const char *debar, int arfd, int gzfd)
@@ -476,7 +482,7 @@ write_format_zero_deb_header(const char *debar, int arfd, int gzfd)
 }
 
 static void
-write_deb_header(const char *debar, int arfd, int gzfd)
+write_deb_header(const char *debar, int arfd, int gzfd, time_t time)
 {
   const char deb_magic[] = ARCHIVEVERSION "\n";
 
@@ -490,8 +496,8 @@ write_deb_header(const char *debar, int arfd, int gzfd)
   }
 
   dpkg_ar_put_magic(debar, arfd);
-  dpkg_ar_member_put_mem(debar, arfd, DEBMAGIC, deb_magic, strlen(deb_magic));
-  dpkg_ar_member_put_file(debar, arfd, ADMINMEMBER, gzfd, -1);
+  dpkg_ar_member_put_mem(debar, arfd, DEBMAGIC, deb_magic, time, strlen(deb_magic));
+  dpkg_ar_member_put_file(debar, arfd, ADMINMEMBER, gzfd, time, -1);
 }
 
 static int
@@ -514,8 +520,9 @@ setup_temp_gz(char const * target_name)
   return gzfd;
 }
 
+#define MTIME_ARG_LENGTH 30 /* length of --mtime=@18446744073709551616 + \0 */
 static void
-create_data_tar(const char *dir, int gzfd)
+create_data_tar(const char *dir, int gzfd, time_t mtime)
 {
   int p1[2], p2[2], p3[2], p4[2];
   pid_t c1, c2, c3, c4;
@@ -528,11 +535,14 @@ create_data_tar(const char *dir, int gzfd)
   m_pipe(p2);
   c1 = subproc_fork();
   if (!c1) {
+    char mtime_arg[MTIME_ARG_LENGTH];
+
+    snprintf(mtime_arg, MTIME_ARG_LENGTH, "--mtime=@%lu", mtime);
     m_dup2(p1[0], 0); close(p1[0]); close(p1[1]);
     m_dup2(p2[1], 1); close(p2[0]); close(p2[1]);
     if (chdir(dir))
       ohshite(_("failed to chdir to `%.255s'"), dir);
-    execlp(TAR, "tar", "-cf", "-", "--format=gnu", "--null", "-T", "-", "--no-recursion", NULL);
+    execlp(TAR, "tar", "-cf", "-", "--format=gnu", "--null", mtime_arg, "-T", "-", "--no-recursion", NULL);
     ohshite(_("unable to execute %s (%s)"), "tar -cf", TAR);
   }
   close(p1[0]);
@@ -598,9 +608,10 @@ create_data_tar(const char *dir, int gzfd)
   subproc_wait_check(c2, _("<compress> from tar -cf"), 0);
   subproc_wait_check(c1, "tar -cf", 0);
 }
+#undef MTIME_ARG_LENGTH
 
 static void
-write_data_tar(const char *debar, int arfd, int gzfd)
+write_data_tar(const char *debar, int arfd, int gzfd, time_t time)
 {
   char datamember[16 + 1];
 
@@ -610,7 +621,7 @@ write_data_tar(const char *debar, int arfd, int gzfd)
   if (lseek(gzfd, 0, SEEK_SET))
     ohshite(_("failed to rewind temporary file (%s)"), _("data member"));
 
-  dpkg_ar_member_put_file(debar, arfd, datamember, gzfd, -1);
+  dpkg_ar_member_put_file(debar, arfd, datamember, gzfd, time, -1);
 }
 
 static void
@@ -683,12 +694,15 @@ do_build(const char *const *argv)
 {
   const char *debar, *dir;
   bool subdir;
+  time_t build_timestamp;
   int arfd;
   int gzfd;
 
   /* Decode our arguments. */
   decode_arguments(argv, &dir, &subdir, &debar);
 
+  build_timestamp = time(NULL);
+
   /* Perform some sanity checks on the to-be-build package. */
   check_build_sanity(dir, subdir, &debar);
 
@@ -698,11 +712,11 @@ do_build(const char *const *argv)
 
   gzfd = setup_temp_gz(_("control member"));
 
-  create_control_tar(dir, gzfd);
+  create_control_tar(dir, gzfd, build_timestamp);
 
   /* We have our first file for the ar-archive. Write a header for it
    * to the package and insert it. */
-  write_deb_header(debar, arfd, gzfd);
+  write_deb_header(debar, arfd, gzfd, build_timestamp);
 
   close(gzfd);
 
@@ -715,10 +729,10 @@ do_build(const char *const *argv)
   } else {
     gzfd = setup_temp_gz(_("data member"));
   }
-  create_data_tar(dir, gzfd);
+  create_data_tar(dir, gzfd, build_timestamp);
   /* Okay, we have data.tar as well now, add it to the ar wrapper. */
   if (deb_format.major != 0) {
-    write_data_tar(debar, arfd, gzfd);
+    write_data_tar(debar, arfd, gzfd, build_timestamp);
     if (fsync(gzfd))
       ohshite(_("unable to close temporary file for data member"));
   }
diff --git a/dpkg-split/split.c b/dpkg-split/split.c
index c8f9f5c..802ebe2 100644
--- a/dpkg-split/split.c
+++ b/dpkg-split/split.c
@@ -217,13 +217,13 @@ mksplit(const char *file_src, const char *prefix, off_t maxpartsize,
 		              (intmax_t)st.st_size, (intmax_t)partsize,
 		              curpart, nparts, arch);
 		dpkg_ar_member_put_mem(file_dst.buf, fd_dst, PARTMAGIC,
-		                       partmagic.buf, partmagic.used);
+		                       partmagic.buf, time(NULL), partmagic.used);
 		varbuf_reset(&partmagic);
 
 		/* Write the data part. */
 		varbuf_printf(&partname, "data.%d", curpart);
 		dpkg_ar_member_put_file(file_dst.buf, fd_dst, partname.buf,
-		                        fd_src, cur_partsize);
+		                        fd_src, time(NULL), cur_partsize);
 		varbuf_reset(&partname);
 
 		close(fd_dst);
diff --git a/lib/dpkg/ar.c b/lib/dpkg/ar.c
index 3c07a59..3e937c5 100644
--- a/lib/dpkg/ar.c
+++ b/lib/dpkg/ar.c
@@ -89,7 +89,7 @@ dpkg_ar_put_magic(const char *ar_name, int ar_fd)
 
 void
 dpkg_ar_member_put_header(const char *ar_name, int ar_fd,
-                          const char *name, off_t size)
+                          const char *name, time_t timestamp, off_t size)
 {
 	char header[sizeof(struct ar_hdr) + 1];
 	int n;
@@ -100,7 +100,7 @@ dpkg_ar_member_put_header(const char *ar_name, int ar_fd,
 		ohshit(_("ar member size %jd too large"), size);
 
 	n = sprintf(header, "%-16s%-12lu0     0     100644  %-10jd`\n",
-	            name, time(NULL), (intmax_t)size);
+	            name, timestamp, (intmax_t)size);
 	if (n != sizeof(struct ar_hdr))
 		ohshit(_("generated corrupt ar header for '%s'"), ar_name);
 
@@ -110,9 +110,9 @@ dpkg_ar_member_put_header(const char *ar_name, int ar_fd,
 
 void
 dpkg_ar_member_put_mem(const char *ar_name, int ar_fd,
-                       const char *name, const void *data, size_t size)
+                       const char *name, const void *data, time_t timestamp, size_t size)
 {
-	dpkg_ar_member_put_header(ar_name, ar_fd, name, size);
+	dpkg_ar_member_put_header(ar_name, ar_fd, name, timestamp, size);
 
 	/* Copy data contents. */
 	if (fd_write(ar_fd, data, size) < 0)
@@ -125,7 +125,7 @@ dpkg_ar_member_put_mem(const char *ar_name, int ar_fd,
 
 void
 dpkg_ar_member_put_file(const char *ar_name, int ar_fd,
-                        const char *name, int fd, off_t size)
+                        const char *name, int fd, time_t timestamp, off_t size)
 {
 	struct dpkg_error err;
 
@@ -137,7 +137,7 @@ dpkg_ar_member_put_file(const char *ar_name, int ar_fd,
 		size = st.st_size;
 	}
 
-	dpkg_ar_member_put_header(ar_name, ar_fd, name, size);
+	dpkg_ar_member_put_header(ar_name, ar_fd, name, timestamp, size);
 
 	/* Copy data contents. */
 	if (fd_fd_copy(fd, ar_fd, size, &err) < 0)
diff --git a/lib/dpkg/ar.h b/lib/dpkg/ar.h
index ec822c8..af7997b 100644
--- a/lib/dpkg/ar.h
+++ b/lib/dpkg/ar.h
@@ -43,11 +43,11 @@ bool dpkg_ar_member_is_illegal(struct ar_hdr *arh);
 
 void dpkg_ar_put_magic(const char *ar_name, int ar_fd);
 void dpkg_ar_member_put_header(const char *ar_name, int ar_fd,
-                               const char *name, off_t size);
+                               const char *name, time_t timestamp, off_t size);
 void dpkg_ar_member_put_file(const char *ar_name, int ar_fd, const char *name,
-                             int fd, off_t size);
+                             int fd, time_t timestamp, off_t size);
 void dpkg_ar_member_put_mem(const char *ar_name, int ar_fd, const char *name,
-                            const void *data, size_t size);
+                            const void *data, time_t timestamp, size_t size);
 off_t dpkg_ar_member_get_size(const char *ar_name, struct ar_hdr *arh);
 
 /** @} */
-- 
1.7.10.4

From ee1bd0fbc681de462853b9068048c82d1cd31747 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Bobbio?= <lu...@debian.org>
Date: Tue, 27 Aug 2013 23:53:22 +0200
Subject: [PATCH 2/3] dpkg-buildpackage: produce the same timestamp in .deb
 and .changes

When using dpkg-buildpackage, we now record a timestamp at the beginning of the
build and use it to build .deb and .changes.

We use the DEB_BUILD_TIMESTAMP environment variable to pass the timestamp to
dpkg-deb. The later will simply use the current time if the former is unset.
---
 dpkg-deb/build.c             |   22 ++++++++++++++++++++--
 scripts/dpkg-buildpackage.pl |    7 +++++++
 2 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/dpkg-deb/build.c b/dpkg-deb/build.c
index 73e5bdd..0ec6e27 100644
--- a/dpkg-deb/build.c
+++ b/dpkg-deb/build.c
@@ -686,6 +686,24 @@ initialize_ar(const char *debar)
   return arfd;
 }
 
+static time_t
+get_build_timestamp(void)
+{
+  time_t timestamp;
+  const char *value;
+  char *end;
+
+  errno = 0;
+  value = getenv("DEB_BUILD_TIMESTAMP");
+  if (!value)
+    return time(NULL);
+
+  timestamp = strtol(value, &end, 10);
+  if (value == end || *end || errno != 0)
+    ohshite(_("unable to parse timestamp `%.255s'"), value);
+  return timestamp;
+}
+
 /**
  * Overly complex function that builds a .deb file.
  */
@@ -698,11 +716,11 @@ do_build(const char *const *argv)
   int arfd;
   int gzfd;
 
+  build_timestamp = get_build_timestamp();
+
   /* Decode our arguments. */
   decode_arguments(argv, &dir, &subdir, &debar);
 
-  build_timestamp = time(NULL);
-
   /* Perform some sanity checks on the to-be-build package. */
   check_build_sanity(dir, subdir, &debar);
 
diff --git a/scripts/dpkg-buildpackage.pl b/scripts/dpkg-buildpackage.pl
index 710a261..4173ad3 100755
--- a/scripts/dpkg-buildpackage.pl
+++ b/scripts/dpkg-buildpackage.pl
@@ -124,6 +124,7 @@ my $binarytarget = 'binary';
 my $targetarch = my $targetgnusystem = '';
 my $call_target = '';
 my $call_target_as_root = 0;
+my $timestamp = time;
 my (@checkbuilddep_opts, @changes_opts, @source_opts);
 
 use constant BUILD_DEFAULT    => 1;
@@ -271,6 +272,8 @@ if ($< == 0) {
     }
 }
 
+$ENV{DEB_BUILD_TIMESTAMP} = $timestamp;
+
 my $build_opts = Dpkg::BuildOptions->new();
 if (defined $parallel) {
     $parallel = $build_opts->get('parallel') if $build_opts->has('parallel');
@@ -440,6 +443,10 @@ if (defined($changedby)) { push @changes_opts, "-e$changedby" }
 if (defined($since)) { push @changes_opts, "-v$since" }
 if (defined($desc)) { push @changes_opts, "-C$desc" }
 
+chomp(my $date822 = `date -R -d\@$timestamp`);
+$? && subprocerr('date -R');
+push @changes_opts, "-DDate=$date822";
+
 my $chg = "../$pva.changes";
 print STDERR " dpkg-genchanges @changes_opts >$chg\n";
 open my $changes_fh, '-|', 'dpkg-genchanges', @changes_opts
-- 
1.7.10.4

From 4d4ef68d88a652b5fd91ef08c22027bb65cc1009 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Bobbio?= <lu...@debian.org>
Date: Wed, 28 Aug 2013 18:22:44 +0200
Subject: [PATCH 3/3] Allow to preset dpkg-buildpackage timestamp

We now allow DEB_BUILD_TIMESTAMP to be externaly set before running
dpkg-buildpackage.

This enables to independently reproduce a previously built package by looking
at the Date field of its .changes file.
---
 scripts/dpkg-buildpackage.pl |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/dpkg-buildpackage.pl b/scripts/dpkg-buildpackage.pl
index 4173ad3..d9f45d3 100755
--- a/scripts/dpkg-buildpackage.pl
+++ b/scripts/dpkg-buildpackage.pl
@@ -124,7 +124,7 @@ my $binarytarget = 'binary';
 my $targetarch = my $targetgnusystem = '';
 my $call_target = '';
 my $call_target_as_root = 0;
-my $timestamp = time;
+my $timestamp = $ENV{DEB_BUILD_TIMESTAMP} || time;
 my (@checkbuilddep_opts, @changes_opts, @source_opts);
 
 use constant BUILD_DEFAULT    => 1;
-- 
1.7.10.4

Attachment: signature.asc
Description: Digital signature

Reply via email to