ZipFileSystem and the impact of JDK-8031748 on ordering of MANIFEST files in the created jars
Jaikiran Pai
jai.forums2013 at gmail.com
Thu Nov 14 15:40:03 UTC 2019
Adding core-libs-dev, since this also involves java.util.jar APIs.
-Jaikiran
On 14/11/19 8:47 PM, Jaikiran Pai wrote:
> Please consider the code listed at the end of this mail. What it does is
> uses ZipFileSystem to create 2 jar files with identical content. foo.jar
> and bar.jar. Both these jar files contain a (random) text file and a
> META-INF/MANIFEST.MF.
>
> In case of foo.jar, the text file gets created first and then the
> META-INF/MANIFEST.MF and the filesystem finally gets closed
>
> In case of bar.jar, the META-INF/MANIFEST.MF gets created first and then
> the text file and the filesystem finally gets closed.
>
> Once both these (identical) jars are created, the JarInputStream class
> is then used to open these jars and get hold of the Manifest file. What
> results is - the JarInputStream returns a null Manifest for foo.jar (the
> one where the META-INF/MANIFEST.MF wasn't created first), whereas the
> JarInputStream for bar.jar rightly finds the Manifest and its correct
> content.
>
> First of all it's a surprise that the JarInputStream seemingly "loses"
> the Manifest if the META-INF/MANIFEST.MF wasn't created first. But given
> that it's already a known issue reported in JBS
> https://bugs.openjdk.java.net/browse/JDK-8031748, at least we (the
> libraries that create jar files know what to do or how to deal with it).
>
> However, when using something like a zipfs FileSystem, where the code
> which deals with writing out content to the filesystem using
> standard/basic java.nio.file.Path and outputstreams, its hard to keep
> track (within the libraries or user code) of the order in which the
> files get written out. So is there any way this (undocumented)
> requirement be implemented as an internal detail within the zipfs
> filesystem implementation, such that it orders the META-INF/MANIFEST.MF
> entry correctly? Or is there a way JarInputStream itself can be fixed to
> not mandate this requirement?
>
> This issue was reported in the Quarkus project
> https://github.com/quarkusio/quarkus/issues/5399 and a workaround has
> been proposed, but given how involved the code is (unrelated to this
> issue), it's going to become more and more difficult to manage this
> ordering.
>
>
> Code reproducing this issue follows:
>
> import java.io.*;
> import java.nio.file.*;
> import java.util.*;
> import java.util.jar.*;
> import java.net.*;
> import static java.nio.file.StandardOpenOption.*;
>
> public class ZipFSJar {
> public static void main(final String[] args) throws Exception {
> final Map<String, String> options =
> Collections.singletonMap("create", "true");
> final Path jarPath = Paths.get("foo.jar");
> try (final FileSystem zipFs =
> FileSystems.newFileSystem(URI.create("jar:" + jarPath.toUri()), options)) {
> // first write non-manifest content
> writeTxtFile(zipFs.getPath("foo.txt"));
> // now write manifest
> Files.createDirectories(zipFs.getPath("META-INF"));
> final Path manifestPath = zipFs.getPath("META-INF",
> "MANIFEST.MF");
> writeManifest(manifestPath, "foo.bar.Baz");
> }
>
> // repeat for bar.jar but with manifest being written out first
> final Path barJar = Paths.get("bar.jar");
> try (final FileSystem zipFs =
> FileSystems.newFileSystem(URI.create("jar:" + barJar.toUri()), options)) {
> Files.createDirectories(zipFs.getPath("META-INF"));
> final Path manifestPath = zipFs.getPath("META-INF",
> "MANIFEST.MF");
> // first write manifest content
> writeManifest(manifestPath, "foo.bar.Baz");
> // now write text file
> writeTxtFile(zipFs.getPath("foo.txt"));
> }
>
> // now check the jar contents
> final Path[] jars = new Path[] {jarPath, barJar};
> for (final Path p : jars) {
> try (InputStream fileInputStream = new
> FileInputStream(p.toFile());
> final JarInputStream jaris = new
> JarInputStream(fileInputStream);) {
> final Manifest manifest = jaris.getManifest();
> if (manifest == null) {
> System.err.println(p + " is missing the manifest file");
> } else {
> System.out.println(p + " has the manifest file");
> final String mainClass =
> manifest.getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);
> if (!"foo.bar.Baz".equals(mainClass)) {
> System.err.println("Found unexpected main class
> " + mainClass + " in jar " + p);
> }
> }
> }
> }
>
> }
>
> private static void writeTxtFile(final Path filePath) throws
> IOException {
> try (final OutputStream os = Files.newOutputStream(filePath)) {
> final byte[] someData = new byte[]{'b', 'c', 'd'};
> os.write(someData);
> }
> }
>
> private static void writeManifest(final Path manifestPath, final
> String mainClass) throws IOException {
> try (final OutputStream os = Files.newOutputStream(manifestPath)) {
> final Manifest manifest = new Manifest();
> final Attributes attributes = manifest.getMainAttributes();
> attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
> attributes.put(Attributes.Name.MAIN_CLASS, mainClass);
> manifest.write(os);
> }
> }
> }
>
More information about the core-libs-dev
mailing list