# HG changeset patch # User kizune # Date 1382538947 -14400 # Node ID e30c5696b4c55244656491a6e139dcff540a4721 # Parent 21c6a82adf04234c9cfb3c400c95c1a59524a27b 8020802: Need an ability to create jar files that are invariant to the pack200 packing/unpacking Reviewed-by: alanb, ksrini diff -r 21c6a82adf04 -r e30c5696b4c5 jdk/src/share/classes/sun/tools/jar/Main.java --- a/jdk/src/share/classes/sun/tools/jar/Main.java Wed Oct 23 15:55:31 2013 +0200 +++ b/jdk/src/share/classes/sun/tools/jar/Main.java Wed Oct 23 18:35:47 2013 +0400 @@ -31,6 +31,7 @@ import java.util.*; import java.util.zip.*; import java.util.jar.*; +import java.util.jar.Pack200.*; import java.util.jar.Manifest; import java.text.MessageFormat; import sun.misc.JarIndex; @@ -72,8 +73,9 @@ * flag0: no zip compression (store only) * Mflag: DO NOT generate a manifest file (just ZIP) * iflag: generate jar index + * nflag: Perform jar normalization at the end */ - boolean cflag, uflag, xflag, tflag, vflag, flag0, Mflag, iflag; + boolean cflag, uflag, xflag, tflag, vflag, flag0, Mflag, iflag, nflag; static final String MANIFEST_DIR = "META-INF/"; static final String VERSION = "1.0"; @@ -197,12 +199,56 @@ vflag = false; } } + File tmpfile = null; + final OutputStream finalout = out; + final String tmpbase = (fname == null) + ? "tmpjar" + : fname.substring(fname.indexOf(File.separatorChar) + 1); + if (nflag) { + tmpfile = createTemporaryFile(tmpbase, ".jar"); + out = new FileOutputStream(tmpfile); + } expand(null, files, false); create(new BufferedOutputStream(out, 4096), manifest); if (in != null) { in.close(); } out.close(); + if(nflag) { + JarFile jarFile = null; + File packFile = null; + JarOutputStream jos = null; + try { + Packer packer = Pack200.newPacker(); + Map p = packer.properties(); + p.put(Packer.EFFORT, "1"); // Minimal effort to conserve CPU + jarFile = new JarFile(tmpfile.getCanonicalPath()); + packFile = createTemporaryFile(tmpbase, ".pack"); + out = new FileOutputStream(packFile); + packer.pack(jarFile, out); + jos = new JarOutputStream(finalout); + Unpacker unpacker = Pack200.newUnpacker(); + unpacker.unpack(packFile, jos); + } catch (IOException ioe) { + fatalError(ioe); + } finally { + if (jarFile != null) { + jarFile.close(); + } + if (out != null) { + out.close(); + } + if (jos != null) { + jos.close(); + } + if (tmpfile != null && tmpfile.exists()) { + tmpfile.delete(); + } + if (packFile != null && packFile.exists()) { + packFile.delete(); + } + } + } } else if (uflag) { File inputFile = null, tmpFile = null; FileInputStream in; @@ -358,6 +404,9 @@ rootjar = args[count++]; iflag = true; break; + case 'n': + nflag = true; + break; case 'e': ename = args[count++]; break; @@ -1215,4 +1264,34 @@ e.setCrc(crc.getValue()); } } + + /** + * Attempt to create temporary file in the system-provided temporary folder, if failed attempts + * to create it in the same folder as the file in parameter (if any) + */ + private File createTemporaryFile(String tmpbase, String suffix) { + File tmpfile = null; + + try { + tmpfile = File.createTempFile(tmpbase, suffix); + } catch (IOException | SecurityException e) { + // Unable to create file due to permission violation or security exception + } + if (tmpfile == null) { + // Were unable to create temporary file, fall back to temporary file in the same folder + if (fname != null) { + try { + File tmpfolder = new File(fname).getAbsoluteFile().getParentFile(); + tmpfile = File.createTempFile(fname, ".tmp" + suffix, tmpfolder); + } catch (IOException ioe) { + // Last option failed - fall gracefully + fatalError(ioe); + } + } else { + // No options left - we can not compress to stdout without access to the temporary folder + fatalError(new IOException(getMsg("error.create.tempfile"))); + } + } + return tmpfile; + } } diff -r 21c6a82adf04 -r e30c5696b4c5 jdk/src/share/classes/sun/tools/jar/resources/jar.properties --- a/jdk/src/share/classes/sun/tools/jar/resources/jar.properties Wed Oct 23 15:55:31 2013 +0200 +++ b/jdk/src/share/classes/sun/tools/jar/resources/jar.properties Wed Oct 23 18:35:47 2013 +0400 @@ -44,6 +44,8 @@ {0} : could not create directory error.incorrect.length=\ incorrect length while processing: {0} +error.create.tempfile=\ + Could not create a temporary file out.added.manifest=\ added manifest out.update.manifest=\ @@ -66,7 +68,7 @@ (in = {0}) (out= {1}) usage=\ -Usage: jar {ctxui}[vfm0Me] [jar-file] [manifest-file] [entry-point] [-C dir] files ...\n\ +Usage: jar {ctxui}[vfmn0Me] [jar-file] [manifest-file] [entry-point] [-C dir] files ...\n\ Options:\n\ \ \ -c create new archive\n\ \ \ -t list table of contents for archive\n\ @@ -75,6 +77,7 @@ \ \ -v generate verbose output on standard output\n\ \ \ -f specify archive file name\n\ \ \ -m include manifest information from specified manifest file\n\ +\ \ -n perform Pack200 normalization after creating a new archive\n\ \ \ -e specify application entry point for stand-alone application \n\ \ \ bundled into an executable jar file\n\ \ \ -0 store only; use no ZIP compression\n\ diff -r 21c6a82adf04 -r e30c5696b4c5 jdk/test/tools/jar/normalize/TestNormal.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/tools/jar/normalize/TestNormal.java Wed Oct 23 18:35:47 2013 +0400 @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * @run main/timeout=600 TestNormal + * @bug 8020802 + * @summary Need an ability to create jar files that are invariant to the pack200 packing/unpacking + * @author Alexander Zuev + */ + +import java.io.*; +import java.util.Collections; +import java.util.Properties; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class TestNormal { + private static String FS = File.separator; + + public static void main(String args[]) throws Exception { + Properties p = System.getProperties(); + String java_home = p.getProperty("test.jdk"); + String dtjar = java_home + File.separator + "lib" + + File.separator + "dt.jar"; + + File folder = new File("dt"); + if (folder.exists()) { + delete(folder); + } + folder.mkdir(); + + try { + extractJar(new JarFile(dtjar), folder); + execJavaCommand(java_home, "jar cnf normalized.jar -C dt ."); + execJavaCommand(java_home, "jar cf original.jar -C dt ."); + execJavaCommand(java_home, "pack200 -r repacked.jar original.jar"); + compareJars(new JarFile("normalized.jar"), new JarFile("repacked.jar")); + } finally { + String[] cleanupList = {"dt", "normalized.jar", "original.jar", "repacked.jar"}; + for (String s : cleanupList) { + delete(new File(s)); + } + } + } + + public static void execJavaCommand(String java_home, String cmd) throws Exception { + Process proc = Runtime.getRuntime().exec(java_home + FS + "bin" + FS + cmd); + String s; + BufferedReader stdInput = + new BufferedReader(new InputStreamReader(proc.getInputStream())); + BufferedReader stdError = + new BufferedReader(new InputStreamReader(proc.getErrorStream())); + while ((s = stdInput.readLine()) != null) { + System.out.println(s); + } + while ((s = stdError.readLine()) != null) { + System.err.println(s); + } + } + + public static void compareJars(JarFile jf1, JarFile jf2) throws Exception { + try { + if (jf1.size() != jf2.size()) { + throw new Exception("Jars " + jf1.getName() + " and " + jf2.getName() + + " have different number of entries"); + } + for (JarEntry elem1 : Collections.list(jf1.entries())) { + JarEntry elem2 = jf2.getJarEntry(elem1.getName()); + if (elem2 == null) { + throw new Exception("Element " + elem1.getName() + " is missing from " + jf2.getName()); + } + if (!elem1.isDirectory() && elem1.getCrc() != elem2.getCrc()) { + throw new Exception("The crc of " + elem1.getName() + " is different."); + } + } + } finally { + jf1.close(); + jf2.close(); + } + } + + public static void extractJar(JarFile jf, File where) throws Exception { + for (JarEntry file : Collections.list(jf.entries())) { + File out = new File(where, file.getName()); + if (file.isDirectory()) { + out.mkdirs(); + continue; + } + File parent = out.getParentFile(); + if (parent != null && !parent.exists()) { + parent.mkdirs(); + } + InputStream is = null; + OutputStream os = null; + try { + is = jf.getInputStream(file); + os = new FileOutputStream(out); + while (is.available() > 0) { + os.write(is.read()); + } + } finally { + if (is != null) { + is.close(); + } + if (os != null) { + os.close(); + } + } + } + } + + static void delete(File f) throws IOException { + if (!f.exists()) { + return; + } + if (f.isDirectory()) { + for (File c : f.listFiles()) { + delete(c); + } + } + if (!f.delete()) { + throw new FileNotFoundException("Failed to delete file: " + f); + } + } +}