Source: imagemagick
Version: 8:6.8.9.9-7
Severity: wishlist
Tags: patch
User: reproducible-bui...@lists.alioth.debian.org
Usertags: toolchain
X-Debbugs-Cc: reproducible-bui...@lists.alioth.debian.org

Dear Maintainer,

While working on the “reproducible builds” effort [1], we have noticed
that some packages (like kannel) use 'convert' in their building
process, resulting in timestamps in postscript files that break
reproducibility.

To solve this kind of issues, it would be nice to have 'convert' support
the SOURCE_DATE_EPOCH environment variable [2].

See the attached patch for a solution to this issue.

Regards,
Alexis Bienvenüe.

[1] https://wiki.debian.org/ReproducibleBuilds
[2] https://reproducible-builds.org/specs/source-date-epoch/

diff -Nru imagemagick-6.8.9.9/debian/changelog imagemagick-6.8.9.9/debian/changelog
--- imagemagick-6.8.9.9/debian/changelog	2016-01-17 21:45:14.000000000 +0100
+++ imagemagick-6.8.9.9/debian/changelog	2016-04-01 01:11:37.000000000 +0200
@@ -1,3 +1,10 @@
+imagemagick (8:6.8.9.9-7.0reproducible1) unstable; urgency=medium
+
+  * Honour the SOURCE_DATE_EPOCH environment variable
+    when writing to files.
+
+ -- Alexis Bienvenüe <p...@passoire.fr>  Fri, 01 Apr 2016 01:11:36 +0200
+
 imagemagick (8:6.8.9.9-7) unstable; urgency=low
 
   * Fix various minor security issues 
diff -Nru imagemagick-6.8.9.9/debian/patches/0076-Honour-SOURCE_DATE_EPOCH-when-writing.patch imagemagick-6.8.9.9/debian/patches/0076-Honour-SOURCE_DATE_EPOCH-when-writing.patch
--- imagemagick-6.8.9.9/debian/patches/0076-Honour-SOURCE_DATE_EPOCH-when-writing.patch	1970-01-01 01:00:00.000000000 +0100
+++ imagemagick-6.8.9.9/debian/patches/0076-Honour-SOURCE_DATE_EPOCH-when-writing.patch	2016-04-03 15:48:56.000000000 +0200
@@ -0,0 +1,307 @@
+Description: Honour the SOURCE_DATE_EPOCH variable when writing
+ Honour the SOURCE_DATE_EPOCH environment variable when writing
+ to some files: PS, PDF, PNG, CIN, DPX, MAT, PDB, setting the timestamp
+ to the provided value and using GM time instead of local time.
+Author: Alexis Bienvenüe <p...@passoire.fr>
+
+Index: imagemagick-6.8.9.9/coders/cin.c
+===================================================================
+--- imagemagick-6.8.9.9.orig/coders/cin.c
++++ imagemagick-6.8.9.9/coders/cin.c
+@@ -971,14 +971,14 @@ static MagickBooleanType WriteCINImage(c
+       sizeof(cin.file.filename));
+   offset+=WriteBlob(image,sizeof(cin.file.filename),(unsigned char *)
+     cin.file.filename);
+-  seconds=time((time_t *) NULL);
+-#if defined(MAGICKCORE_HAVE_LOCALTIME_R)
+-  (void) localtime_r(&seconds,&local_time);
+-#else
+-  (void) memcpy(&local_time,localtime(&seconds),sizeof(local_time));
+-#endif
++  seconds=CurrentTime();
++  (void) LocalOrGMTime(&seconds,&local_time);
+   (void) memset(timestamp,0,sizeof(timestamp));
+-  (void) strftime(timestamp,MaxTextExtent,"%Y:%m:%d:%H:%M:%S%Z",&local_time);
++  if(getenv("SOURCE_DATE_EPOCH")) {
++    (void) strftime(timestamp,MaxTextExtent,"%Y:%m:%d:%H:%M:%SUTC",&local_time);
++  } else {
++    (void) strftime(timestamp,MaxTextExtent,"%Y:%m:%d:%H:%M:%S%Z",&local_time);
++  }
+   (void) memset(cin.file.create_date,0,sizeof(cin.file.create_date));
+   (void) CopyMagickString(cin.file.create_date,timestamp,11);
+   offset+=WriteBlob(image,sizeof(cin.file.create_date),(unsigned char *)
+@@ -1074,9 +1074,6 @@ static MagickBooleanType WriteCINImage(c
+       sizeof(cin.origination.filename));
+   offset+=WriteBlob(image,sizeof(cin.origination.filename),(unsigned char *)
+     cin.origination.filename);
+-  seconds=time((time_t *) NULL);
+-  (void) memset(timestamp,0,sizeof(timestamp));
+-  (void) strftime(timestamp,MaxTextExtent,"%Y:%m:%d:%H:%M:%S%Z",&local_time);
+   (void) memset(cin.origination.create_date,0,
+     sizeof(cin.origination.create_date));
+   (void) CopyMagickString(cin.origination.create_date,timestamp,11);
+Index: imagemagick-6.8.9.9/coders/dpx.c
+===================================================================
+--- imagemagick-6.8.9.9.orig/coders/dpx.c
++++ imagemagick-6.8.9.9/coders/dpx.c
+@@ -1535,7 +1535,7 @@ static MagickBooleanType WriteDPXImage(c
+     (void) strncpy(dpx.file.filename,value,sizeof(dpx.file.filename)-1);
+   offset+=WriteBlob(image,sizeof(dpx.file.filename),(unsigned char *)
+     dpx.file.filename);
+-  seconds=time((time_t *) NULL);
++  seconds=CurrentTime();
+   (void) FormatMagickTime(seconds,sizeof(dpx.file.timestamp),
+     dpx.file.timestamp);
+   offset+=WriteBlob(image,sizeof(dpx.file.timestamp),(unsigned char *)
+Index: imagemagick-6.8.9.9/coders/mat.c
+===================================================================
+--- imagemagick-6.8.9.9.orig/coders/mat.c
++++ imagemagick-6.8.9.9/coders/mat.c
+@@ -1224,12 +1224,8 @@ static MagickBooleanType WriteMATImage(c
+     return(MagickFalse);
+   image->depth=8;
+ 
+-  current_time=time((time_t *) NULL);
+-#if defined(MAGICKCORE_HAVE_LOCALTIME_R)
+-  (void) localtime_r(&current_time,&local_time);
+-#else
+-  (void) memcpy(&local_time,localtime(&current_time),sizeof(local_time));
+-#endif
++  current_time=CurrentTime();
++  (void) LocalOrGMTime(&current_time,&local_time);
+   (void) memset(MATLAB_HDR,' ',MagickMin(sizeof(MATLAB_HDR),124));
+   FormatLocaleString(MATLAB_HDR,sizeof(MATLAB_HDR),
+     "MATLAB 5.0 MAT-file, Platform: %s, Created on: %s %s %2d %2d:%2d:%2d %d",
+Index: imagemagick-6.8.9.9/coders/pdb.c
+===================================================================
+--- imagemagick-6.8.9.9.orig/coders/pdb.c
++++ imagemagick-6.8.9.9/coders/pdb.c
+@@ -762,7 +762,7 @@ static MagickBooleanType WritePDBImage(c
+   (void) CopyMagickString(pdb_info.name,image_info->filename,32);
+   pdb_info.attributes=0;
+   pdb_info.version=0;
+-  pdb_info.create_time=time(NULL);
++  pdb_info.create_time=CurrentTime();
+   pdb_info.modify_time=pdb_info.create_time;
+   pdb_info.archive_time=0;
+   pdb_info.modify_number=0;
+Index: imagemagick-6.8.9.9/coders/pdf.c
+===================================================================
+--- imagemagick-6.8.9.9.orig/coders/pdf.c
++++ imagemagick-6.8.9.9/coders/pdf.c
+@@ -1261,7 +1261,7 @@ RestoreMSCWarning
+       value=GetImageProperty(image,"date:create");
+       if (value != (const char *) NULL)
+         (void) CopyMagickString(create_date,value,MaxTextExtent);
+-      (void) FormatMagickTime(time((time_t *) NULL),MaxTextExtent,timestamp);
++      (void) FormatMagickTime(CurrentTime(),MaxTextExtent,timestamp);
+       i=FormatLocaleString(xmp_profile,MaxTextExtent,XMPProfile,
+         XMPProfileMagick,modify_date,create_date,timestamp,
+         GetMagickVersion(&version),EscapeParenthesis(basename),
+@@ -2669,12 +2669,8 @@ RestoreMSCWarning
+   (void) FormatLocaleString(buffer,MaxTextExtent,"/Title (%s)\n",
+     EscapeParenthesis(basename));
+   (void) WriteBlobString(image,buffer);
+-  seconds=time((time_t *) NULL);
+-#if defined(MAGICKCORE_HAVE_LOCALTIME_R)
+-  (void) localtime_r(&seconds,&local_time);
+-#else
+-  (void) memcpy(&local_time,localtime(&seconds),sizeof(local_time));
+-#endif
++  seconds=CurrentTime();
++  (void) LocalOrGMTime(&seconds,&local_time);
+   (void) FormatLocaleString(date,MaxTextExtent,"D:%04d%02d%02d%02d%02d%02d",
+     local_time.tm_year+1900,local_time.tm_mon+1,local_time.tm_mday,
+     local_time.tm_hour,local_time.tm_min,local_time.tm_sec);
+Index: imagemagick-6.8.9.9/coders/png.c
+===================================================================
+--- imagemagick-6.8.9.9.orig/coders/png.c
++++ imagemagick-6.8.9.9/coders/png.c
+@@ -7735,7 +7735,7 @@ static void write_tIME_chunk(Image *imag
+     }
+   else
+   {
+-    time(&ttime);
++    ttime=CurrentTime();
+     png_convert_from_time_t(&ptime,ttime);
+   }
+   png_set_tIME(ping,info,&ptime);
+Index: imagemagick-6.8.9.9/coders/ps.c
+===================================================================
+--- imagemagick-6.8.9.9.orig/coders/ps.c
++++ imagemagick-6.8.9.9/coders/ps.c
+@@ -1643,7 +1643,7 @@ static MagickBooleanType WritePSImage(co
+         (void) FormatLocaleString(buffer,MaxTextExtent,"%%%%Title: (%s)\n",
+           image->filename);
+         (void) WriteBlobString(image,buffer);
+-        timer=time((time_t *) NULL);
++        timer=CurrentTime();
+         (void) FormatMagickTime(timer,MaxTextExtent,date);
+         (void) FormatLocaleString(buffer,MaxTextExtent,
+           "%%%%CreationDate: (%s)\n",date);
+Index: imagemagick-6.8.9.9/coders/ps2.c
+===================================================================
+--- imagemagick-6.8.9.9.orig/coders/ps2.c
++++ imagemagick-6.8.9.9/coders/ps2.c
+@@ -568,7 +568,7 @@ static MagickBooleanType WritePS2Image(c
+         (void) FormatLocaleString(buffer,MaxTextExtent,"%%%%Title: (%s)\n",
+           image->filename);
+         (void) WriteBlobString(image,buffer);
+-        timer=time((time_t *) NULL);
++        timer=CurrentTime();
+         (void) FormatMagickTime(timer,MaxTextExtent,date);
+         (void) FormatLocaleString(buffer,MaxTextExtent,
+           "%%%%CreationDate: (%s)\n",date);
+Index: imagemagick-6.8.9.9/coders/ps3.c
+===================================================================
+--- imagemagick-6.8.9.9.orig/coders/ps3.c
++++ imagemagick-6.8.9.9/coders/ps3.c
+@@ -1008,7 +1008,7 @@ static MagickBooleanType WritePS3Image(c
+         (void) FormatLocaleString(buffer,MaxTextExtent,"%%%%Title: %s\n",
+           image->filename);
+         (void) WriteBlobString(image,buffer);
+-        timer=time((time_t *) NULL);
++        timer=CurrentTime();
+         (void) FormatMagickTime(timer,MaxTextExtent,date);
+         (void) FormatLocaleString(buffer,MaxTextExtent,
+           "%%%%CreationDate: %s\n",date);
+Index: imagemagick-6.8.9.9/magick/string.c
+===================================================================
+--- imagemagick-6.8.9.9.orig/magick/string.c
++++ imagemagick-6.8.9.9/magick/string.c
+@@ -1126,6 +1126,81 @@ MagickExport ssize_t FormatMagickSize(co
+   return(count);
+ }
+ 
++/* CurrentTime()
++   returns the source time from SOURCE_DATE_EPOCH environment variable (if set),
++   or current time.
++
++   code from https://wiki.debian.org/ReproducibleBuilds/TimestampsProposal#C
++*/
++
++MagickExport time_t CurrentTime()
++{
++  struct tm *build_time;
++  time_t now;
++  unsigned long long epoch;
++  char *endptr;
++  char *source_date_epoch=getenv("SOURCE_DATE_EPOCH");
++  if (source_date_epoch) {
++    errno = 0;
++    epoch = strtoull(source_date_epoch, &endptr, 10);
++    if ((errno == ERANGE && (epoch == ULLONG_MAX || epoch == 0))
++        || (errno != 0 && epoch == 0)) {
++      fprintf(stderr, "Environment variable $SOURCE_DATE_EPOCH: strtoull: %s\n", strerror(errno));
++      exit(EXIT_FAILURE);
++    }
++    if (endptr == source_date_epoch) {
++      fprintf(stderr, "Environment variable $SOURCE_DATE_EPOCH: No digits were found: %s\n", endptr);
++      exit(EXIT_FAILURE);
++    }
++    if (*endptr != '\0') {
++      fprintf(stderr, "Environment variable $SOURCE_DATE_EPOCH: Trailing garbage: %s\n", endptr);
++      exit(EXIT_FAILURE);
++    }
++    if (epoch > ULONG_MAX) {
++      fprintf(stderr, "Environment variable $SOURCE_DATE_EPOCH: value must be smaller than or equal to: %lu but was found to be: %llu \n", ULONG_MAX, epoch);
++      exit(EXIT_FAILURE);
++    }
++    now = epoch;
++  } else {
++    now = time(NULL);
++  }
++  return(now);
++}
++
++/* LocalOrGMTime computes the local or GM time from s, and stores it to t.
++   If the SOURCE_DATE_EPOCH environment variable is set, GM time is used,
++   otherwise local time is used.
++ */
++MagickExport void LocalOrGMTime(time_t *s,struct tm *t) {
++  if(getenv("SOURCE_DATE_EPOCH") != NULL) {
++#if defined(MAGICKCORE_HAVE_GMTIME_R)
++    (void) gmtime_r(s,t);
++#else
++    {
++      struct tm
++        *my_time;
++
++      my_time=gmtime(t);
++      if (my_time != (struct tm *) NULL)
++        (void) memcpy(t,my_time,sizeof(*t));
++    }
++#endif
++  } else {
++#if defined(MAGICKCORE_HAVE_LOCALTIME_R)
++    (void) localtime_r(s,t);
++#else
++    {
++      struct tm
++        *my_time;
++
++      my_time=localtime(t);
++      if (my_time != (struct tm *) NULL)
++        (void) memcpy(t,my_time,sizeof(*t));
++    }
++#endif
++  }
++}
++
+ /*
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %                                                                             %
+@@ -1195,14 +1270,21 @@ MagickExport ssize_t FormatMagickTime(co
+       (void) memcpy(&gm_time,my_time,sizeof(gm_time));
+   }
+ #endif
+-  timezone=(time_t) ((local_time.tm_min-gm_time.tm_min)/60+
+-    local_time.tm_hour-gm_time.tm_hour+24*((local_time.tm_year-
+-    gm_time.tm_year) != 0 ? (local_time.tm_year-gm_time.tm_year) :
+-    (local_time.tm_yday-gm_time.tm_yday)));
+-  count=FormatLocaleString(timestamp,length,
+-    "%04d-%02d-%02dT%02d:%02d:%02d%+03ld:00",local_time.tm_year+1900,
+-    local_time.tm_mon+1,local_time.tm_mday,local_time.tm_hour,
+-    local_time.tm_min,local_time.tm_sec,(long) timezone);
++  if(getenv("SOURCE_DATE_EPOCH")) {
++    count=FormatLocaleString(timestamp,length,
++     "%04d-%02d-%02dT%02d:%02d:%02d%+03ld:00",gm_time.tm_year+1900,
++     gm_time.tm_mon+1,gm_time.tm_mday,gm_time.tm_hour,
++     gm_time.tm_min,gm_time.tm_sec,0);
++  } else {
++    timezone=(time_t) ((local_time.tm_min-gm_time.tm_min)/60+
++                       local_time.tm_hour-gm_time.tm_hour+24*((local_time.tm_year-
++                       gm_time.tm_year) != 0 ? (local_time.tm_year-gm_time.tm_year) :
++                       (local_time.tm_yday-gm_time.tm_yday)));
++    count=FormatLocaleString(timestamp,length,
++     "%04d-%02d-%02dT%02d:%02d:%02d%+03ld:00",local_time.tm_year+1900,
++     local_time.tm_mon+1,local_time.tm_mday,local_time.tm_hour,
++     local_time.tm_min,local_time.tm_sec,(long) timezone);
++  }
+   return(count);
+ }
+ 
+Index: imagemagick-6.8.9.9/magick/string_.h
+===================================================================
+--- imagemagick-6.8.9.9.orig/magick/string_.h
++++ imagemagick-6.8.9.9/magick/string_.h
+@@ -83,6 +83,9 @@ extern MagickExport ssize_t
+   FormatMagickSize(const MagickSizeType,const MagickBooleanType,char *),
+   FormatMagickTime(const time_t,const size_t,char *);
+ 
++extern MagickExport time_t
++  CurrentTime();
++
+ extern MagickExport StringInfo
+   *AcquireStringInfo(const size_t),
+   *BlobToStringInfo(const void *,const size_t),
+@@ -107,7 +110,8 @@ extern MagickExport void
+   SetStringInfoDatum(StringInfo *,const unsigned char *),
+   SetStringInfoLength(StringInfo *,const size_t),
+   SetStringInfoPath(StringInfo *,const char *),
+-  StripString(char *);
++  StripString(char *),
++  LocalOrGMTime(time_t *,struct tm *);
+ 
+ #if defined(__cplusplus) || defined(c_plusplus)
+ }
diff -Nru imagemagick-6.8.9.9/debian/patches/series imagemagick-6.8.9.9/debian/patches/series
--- imagemagick-6.8.9.9/debian/patches/series	2016-01-17 21:45:18.000000000 +0100
+++ imagemagick-6.8.9.9/debian/patches/series	2016-04-01 01:05:19.000000000 +0200
@@ -74,3 +74,4 @@
 0073-Fixed-memory-leaks.patch
 0074-Fix-overflow-in-pict-image-parsing.patch
 0075-Fix-buffer-overflow-in-icon-parsing-code.patch
+0076-Honour-SOURCE_DATE_EPOCH-when-writing.patch

Reply via email to