Package: dpkg
Version: 1.17.14
Severity: wishlist
Tags: patch
User: reproducible-bui...@lists.alioth.debian.org
Usertags: toolchain, timestamps
X-Debbugs-Cc: reproducible-bui...@lists.alioth.debian.org

Hi!

`.deb` are ar archives. The archive internal headers currently capture
the time when the build was made. As part of the reproducible builds
project [1], it would be great if these timestamps could be made
easily reproducible.

Guillem Jover already expressed [2] that he preferred to keep these
timestamps meaningful. The attached patches will set them to be the date
of the latest changelog entry when a package is built using
`dpkg-buildpackage`. During the discussions at DebConf14, there was a
consensus that it was the most meaningful time reference to use.

The first patch will modify `dpkg-deb` to use the same timestamp for
every member of the ar archive.

The second patch will:

 1. Make `dpkg-deb` try to look for a timestamp to use in the
    DEB_BUILD_TIMESTAMP environment variable in epoch format.
    If not set or not parseable, it will default to use the current
    time.
 2. Change `dpkg-buildpackage` to parse `debian/changelog` and preset
    DEB_BUILD_TIMESTAMP to the value of its latest entry. Unless
    DEB_BUILD_TIMESTAMP was already set in order to allow arbitrary
    dates to be reproduced.

 [1]: https://wiki.debian.org/ReproducibleBuilds
 [2]: https://bugs.debian.org/719844#10

-- 
Lunar                                .''`. 
lu...@debian.org                    : :Ⓐ  :  # apt-get install anarchism
                                    `. `'` 
                                      `-   
From fe1543722750a71ed7d7110291a917fc12bbc7ce 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/2] Use a single timestamp for ar headers when building a
 .deb

In order to make build reproducible in the future, we now use a single
timestamp in all ar headers when creating a .deb.

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.
---
 dpkg-deb/build.c   |   10 +++++++---
 dpkg-split/split.c |    4 ++--
 lib/dpkg/ar.c      |   13 +++++++------
 lib/dpkg/ar.h      |    4 ++--
 4 files changed, 18 insertions(+), 13 deletions(-)

diff --git a/dpkg-deb/build.c b/dpkg-deb/build.c
index 0b9cfb6..5384776 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>
@@ -440,6 +441,7 @@ do_build(const char *const *argv)
   int arfd;
   int p1[2], p2[2], gzfd;
   pid_t c1, c2;
+  time_t build_timestamp;
 
   /* Decode our arguments. */
   dir = *argv++;
@@ -486,6 +488,8 @@ do_build(const char *const *argv)
   }
   m_output(stdout, _("<standard output>"));
 
+  build_timestamp = time(NULL);
+
   /* Now that we have verified everything its time to actually
    * build something. Let's start by making the ar-wrapper. */
   arfd = creat(debar, 0644);
@@ -561,8 +565,8 @@ do_build(const char *const *argv)
             compressor_get_extension(control_compress_params.type));
 
     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, build_timestamp, strlen(deb_magic));
+    dpkg_ar_member_put_file(debar, arfd, adminmember, gzfd, build_timestamp, -1);
   } else {
     internerr("unknown deb format version %d.%d", deb_format.major, deb_format.minor);
   }
@@ -632,7 +636,7 @@ do_build(const char *const *argv)
     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, build_timestamp, -1);
 
     close(gzfd);
   }
diff --git a/dpkg-split/split.c b/dpkg-split/split.c
index 37cbb93..b2da62b 100644
--- a/dpkg-split/split.c
+++ b/dpkg-split/split.c
@@ -216,13 +216,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 cf540a0..8613310 100644
--- a/lib/dpkg/ar.c
+++ b/lib/dpkg/ar.c
@@ -36,11 +36,12 @@
 #include <dpkg/ar.h>
 
 static void
-dpkg_ar_member_init(struct dpkg_ar_member *member, const char *name, off_t size)
+dpkg_ar_member_init(struct dpkg_ar_member *member, const char *name,
+                    time_t timestamp, off_t size)
 {
 	member->name = name;
 	member->size = size;
-	member->time = time(NULL);
+	member->time = timestamp;
 	member->mode = 0100644;
 	member->uid = 0;
 	member->gid = 0;
@@ -124,11 +125,11 @@ 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)
 {
 	struct dpkg_ar_member member;
 
-	dpkg_ar_member_init(&member, name, size);
+	dpkg_ar_member_init(&member, name, timestamp, size);
 	dpkg_ar_member_put_header(ar_name, ar_fd, &member);
 
 	/* Copy data contents. */
@@ -142,7 +143,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;
 	struct dpkg_ar_member member;
@@ -155,7 +156,7 @@ dpkg_ar_member_put_file(const char *ar_name, int ar_fd,
 		size = st.st_size;
 	}
 
-	dpkg_ar_member_init(&member, name, size);
+	dpkg_ar_member_init(&member, name, timestamp, size);
 	dpkg_ar_member_put_header(ar_name, ar_fd, &member);
 
 	/* Copy data contents. */
diff --git a/lib/dpkg/ar.h b/lib/dpkg/ar.h
index 81a061e..c1b8bed 100644
--- a/lib/dpkg/ar.h
+++ b/lib/dpkg/ar.h
@@ -59,9 +59,9 @@ 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,
                                struct dpkg_ar_member *member);
 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 70c0817ca179aaeaa50e6c5808f3a9deb7aaefc2 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/2] Preset build timestamp to date of the latest changelog
 entry

dpkg-buildpackage will set the DEB_BUILD_TIMESTAMP environment variable to the
date of the latest entry in debian/changelog. This enables build to be easily
reproduced as the dates captured in the `.deb` archives will be deterministic.

DEB_BUILD_TIMESTAMP can also be externaly set before running dpkg-buildpackage.
to reproduce the build with an arbitrary date.
---
 dpkg-deb/build.c             |   20 +++++++++++++++++++-
 scripts/dpkg-buildpackage.pl |    6 ++++++
 2 files changed, 25 insertions(+), 1 deletion(-)

diff --git a/dpkg-deb/build.c b/dpkg-deb/build.c
index 5384776..6ef3563 100644
--- a/dpkg-deb/build.c
+++ b/dpkg-deb/build.c
@@ -427,6 +427,24 @@ pkg_get_pathname(const char *dir, struct pkginfo *pkg)
   return path;
 }
 
+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.
  */
@@ -488,7 +506,7 @@ do_build(const char *const *argv)
   }
   m_output(stdout, _("<standard output>"));
 
-  build_timestamp = time(NULL);
+  build_timestamp = get_build_timestamp();
 
   /* Now that we have verified everything its time to actually
    * build something. Let's start by making the ar-wrapper. */
diff --git a/scripts/dpkg-buildpackage.pl b/scripts/dpkg-buildpackage.pl
index a5385ed..16975a1 100755
--- a/scripts/dpkg-buildpackage.pl
+++ b/scripts/dpkg-buildpackage.pl
@@ -409,6 +409,12 @@ if ($changedby) {
     $maintainer = mustsetvar($changelog->{maintainer}, _g('source changed by'));
 }
 
+if (!$ENV{DEB_BUILD_TIMESTAMP}) {
+    my $timestamp = `date -d'$changelog->{date}' +%s`;
+    chomp $timestamp;
+    $ENV{DEB_BUILD_TIMESTAMP} = $timestamp;
+}
+
 open my $arch_env, '-|', 'dpkg-architecture', "-a$targetarch",
     "-t$targetgnusystem", '-f' or subprocerr('dpkg-architecture');
 while ($_ = <$arch_env>) {
-- 
1.7.10.4

Attachment: signature.asc
Description: Digital signature

Reply via email to