Description: Make the Lisp image reproducible
 The image recorded the build date (date!*, the image birthday and the date of
 each member) in the local time zone and locale. Replacing a member also left
 its old content in the file, a HeapImage of varying size holding timing
 information: the file is now rewritten without it.
Author: Giovanni Mascellani <gio@debian.org>, Emmanuel Bourg <ebourg@apache.org>
Forwarded: no
--- a/src/org/mathpiper/mpreduce/Jlisp.java
+++ b/src/org/mathpiper/mpreduce/Jlisp.java
@@ -63,6 +63,7 @@
 import java.text.DateFormat;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.TimeZone;
 import java.util.Vector;
 import java.util.zip.GZIPInputStream;
@@ -689,9 +690,7 @@
 			// testing.
 			if (!loaded)
 			{   initSymbols();
-				DateFormat df = DateFormat.getInstance();
-				df.setTimeZone(TimeZone.getDefault());
-				lit[Lit.birthday] = new LispString(df.format(new Date()));
+				lit[Lit.birthday] = new LispString(birthday());
 			}
 			else
 			{   // System.out.println("Bodge here...");
@@ -951,6 +950,19 @@
 		lit[Lit.query_io].car/*value*/    = lispIO;
 	}
 
+	// The date stamped into the image, fixed by SOURCE_DATE_EPOCH when it is set
+	static String birthday()
+	{   String epoch = System.getenv("SOURCE_DATE_EPOCH");
+		if (epoch != null)
+		{   DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.ROOT);
+			df.setTimeZone(TimeZone.getTimeZone("UTC"));
+			return df.format(new Date(1000 * Long.parseLong(epoch)));
+		}
+		DateFormat df = DateFormat.getInstance();
+		df.setTimeZone(TimeZone.getDefault());
+		return df.format(new Date());
+	}
+
         //Checkpoint calls this function.
 	public static void preserve(LispObject arg1, LispObject arg2) throws Exception
 	{
@@ -966,10 +978,7 @@
 
 		LispObject oldBirthday = lit[Lit.birthday];
 		// I want the new image file to have a fresh date
-		DateFormat df = DateFormat.getInstance();
-		df.setTimeZone(TimeZone.getDefault());
-		lit[Lit.birthday] =
-		        new LispString(df.format(new Date()));
+		lit[Lit.birthday] = new LispString(birthday());
 
 		GZIPOutputStream dump = null;
 		try
--- a/src/org/mathpiper/mpreduce/functions/builtin/Fns1.java
+++ b/src/org/mathpiper/mpreduce/functions/builtin/Fns1.java
@@ -50,6 +50,8 @@
 import java.text.ParsePosition;
 import java.util.Date;
 import java.util.Iterator;
+import java.util.Locale;
+import java.util.TimeZone;
 import org.mathpiper.mpreduce.Environment;
 
 import org.mathpiper.mpreduce.functions.functionwithenvironment.Bytecode;
@@ -1598,6 +1600,12 @@
 {
     public LispObject op0()
     {
+        String epoch = System.getenv("SOURCE_DATE_EPOCH");
+        if (epoch != null)
+        {   DateFormat df = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.ROOT);
+            df.setTimeZone(TimeZone.getTimeZone("UTC"));
+            return new LispString(df.format(new Date(1000 * Long.parseLong(epoch))));
+        }
         Date now = new Date();
         String s = DateFormat.getDateTimeInstance().format(now);
         return new LispString(s);
--- a/src/org/mathpiper/mpreduce/packagedatastore/PDS.java
+++ b/src/org/mathpiper/mpreduce/packagedatastore/PDS.java
@@ -399,10 +399,51 @@
     } while (p != 0);
 }
 
+// Replacing a member in place leaves its old space vacant, and the size of
+// that stale content varies from one build to another. Rewrite the file
+// without the member instead, the new version is then appended to it.
+void removeMember(String member) throws IOException, ResourceException
+{
+    Vector names = new Vector();
+    byte [] buffer = new byte[DirectoryBlockSize];
+    long p = 0;
+    do
+    {   seek(p);
+        for (int i=0; i<buffer.length; i++) buffer[i] = (byte)read();
+        p = 0;
+        for (int i=0; i<4; i++) p = (p<<8) + (buffer[i+2] & 0xff);
+        int n = 6, l;
+        while ((l = buffer[n++] & 0xff) != 0)
+        {   String nn = new String(buffer, n, l, "UTF8");
+            if (!nn.equals(member)) names.add(nn);
+            n += l + 16;
+        }
+    } while (p != 0);
+    Vector contents = new Vector();
+    for (int i=0; i<names.size(); i++)
+    {   PDSEntry e = (PDSEntry)directory.get(names.get(i));
+        byte [] b = new byte[e.len];
+        f.seek(e.loc);
+        f.readFully(b);
+        contents.add(b);
+    }
+    f.setLength(0);
+    f.close();
+    f = null;
+    directory.clear();
+    for (int i=0; i<names.size(); i++)
+    {   PDSOutputStream out = new PDSOutputStream(this, (String)names.get(i));
+        byte [] b = (byte [])contents.get(i);
+        for (int j=0; j<b.length; j++) out.write(b[j]);
+        out.close();
+    }
+}
+
 void addToDirectory(String member) throws IOException, ResourceException
 {
     if (!writeable)
         throw new IOException("Attempt to update a read-only image file");
+    if (f != null && directory.get(member) != null) removeMember(member);
     Jlisp.println("Image member " + member + " being created");
     long p = 0;
     int n, i;
--- a/src/org/mathpiper/mpreduce/packagedatastore/PDSOutputStream.java
+++ b/src/org/mathpiper/mpreduce/packagedatastore/PDSOutputStream.java
@@ -76,7 +76,7 @@
     pds.f.write(length >> 16);
     pds.f.write(length >> 8);
     pds.f.write(length);
-    long date = new Date().getTime();
+    long date = 0;
     pds.f.write((int)(date >> 56));
     pds.f.write((int)(date >> 48));
     pds.f.write((int)(date >> 40));
