src/share/classes/org/openjdk/jigsaw/ModuleFile.java
Print this page
@@ -31,10 +31,11 @@
import java.util.*;
import java.util.jar.*;
import java.util.zip.*;
import static org.openjdk.jigsaw.FileConstants.ModuleFile.*;
+import static org.openjdk.jigsaw.FileConstants.ModuleFile.SectionType.*;
public final class ModuleFile {
/**
* Return the subdir of a section in an extracted module file.
*/
@@ -66,11 +67,11 @@
private File natlibs;
private File natcmds;
private File configs;
private static class CountingInputStream extends FilterInputStream {
- int count;
+ private int count;
public CountingInputStream(InputStream stream, int count) {
super(stream);
this.count = count;
}
@@ -112,11 +113,16 @@
long skipped = super.skip(n);
if (n > 0)
count-=skipped;
return skipped;
}
+
+ public void close() throws IOException {
+ // Do nothing, CountingInputStream is used to wrap (sub)section
+ // content. We never want to close the underlying stream.
}
+ }
public Reader(DataInputStream stream) {
hashtype = HashType.SHA256;
// Ensure that mark/reset is supported
if (stream.markSupported()) {
@@ -125,20 +131,10 @@
this.stream =
new DataInputStream(new BufferedInputStream(stream));
}
}
- private void checkHashMatch(byte[] expected, byte[] computed)
- throws IOException
- {
- if (!MessageDigest.isEqual(expected, computed))
- throw new IOException("Expected hash "
- + hashHexString(expected)
- + " instead of "
- + hashHexString(computed));
- }
-
private ModuleFileHeader fileHeader = null;
private MessageDigest fileDigest = null;
private MessageDigest sectionDigest = null;
private DataInputStream fileIn = null;
private byte[] moduleInfoBytes = null;
@@ -145,10 +141,11 @@
private Integer moduleSignatureType = null;
private byte[] moduleSignatureBytes = null;
private final int MAX_SECTION_HEADER_LENGTH = 128;
private List<byte[]> calculatedHashes = new ArrayList<>();
private boolean extract = true;
+ private List<String> contents; // list of the module-file contents
/*
* Reads the MODULE_INFO section and the Signature section, if present,
* but does not write any files.
*/
@@ -165,11 +162,11 @@
fileHeader.write(new DataOutputStream(baos));
sectionDigest.update(baos.toByteArray());
calculatedHashes.add(sectionDigest.digest());
fileIn = new DataInputStream(dis);
- if (readSection(fileIn) != SectionType.MODULE_INFO)
+ if (readSection(fileIn) != MODULE_INFO)
throw new IOException("First module-file section"
+ " is not MODULE_INFO");
assert moduleInfoBytes != null;
// Read the Signature Section, if present
@@ -198,18 +195,21 @@
this.deflate = deflate;
this.destination = dst != null ? dst.getCanonicalFile() : null;
this.natlibs = natlibs != null ? natlibs : new File(destination, "lib");
this.natcmds = natcmds != null ? natcmds : new File(destination, "bin");
this.configs = configs != null ? configs : new File(destination, "etc");
+ contents = new ArrayList<>();
+
try {
if (extract)
Files.store(moduleInfoBytes, computeRealPath("info"));
+ contents.add("module-info.class");
// Module-Info and Signature, if present, have been consumed
// Read rest of file until all sections have been read
stream.mark(1);
- while (-1 != stream.read()) {
+ while (stream.read() != -1) {
stream.reset();
readSection(fileIn);
stream.mark(1);
}
@@ -254,25 +254,16 @@
byte[] getSignatureNoClone() {
return moduleSignatureBytes;
}
- private JarOutputStream contentStream = null;
-
- private JarOutputStream contentStream() throws IOException {
- if (contentStream == null) {
- if (extract) {
- FileOutputStream fos
- = new FileOutputStream(computeRealPath("classes"));
- contentStream
- = new JarOutputStream(new BufferedOutputStream(fos));
- } else {
- contentStream = new JarOutputStream(new NullOutputStream());
+ public List<String> getContents() throws IOException {
+ if (contents == null)
+ readModule();
+ Collections.sort(contents);
+ return contents;
}
- }
- return contentStream;
- }
public void close() throws IOException {
try {
try {
if (contentStream != null) {
@@ -306,27 +297,23 @@
private void readSignatureSection(DataInputStream stream,
DigestInputStream dis)
throws IOException
{
-
// Turn off digest computation before reading Signature Section
dis.on(false);
// Mark the starting position
stream.mark(MAX_SECTION_HEADER_LENGTH);
if (stream.read() != -1) {
stream.reset();
SectionHeader header = SectionHeader.read(stream);
- if (header != null &&
- header.getType() == SectionType.SIGNATURE) {
+ if (header != null && header.getType() == SIGNATURE)
readSectionContent(header, stream);
- } else {
- // Revert back to the starting position
- stream.reset();
+ else
+ stream.reset(); // No signature, reset back to start
}
- }
// Turn on digest computation again
dis.on(true);
}
@@ -343,80 +330,211 @@
throws IOException
{
SectionType type = header.getType();
Compressor compressor = header.getCompressor();
int csize = header.getCSize();
- short subsections =
- type.hasFiles() ? header.getSubsections() : 1;
CountingInputStream cs = new CountingInputStream(stream, csize);
sectionDigest.reset();
DigestInputStream dis = new DigestInputStream(cs, sectionDigest);
DataInputStream in = new DataInputStream(dis);
+ if (type.hasFiles()) {
+ short subsections = header.getSubsections();
for (int subsection = 0; subsection < subsections; subsection++)
- readFile(in, compressor, type, csize);
+ readSubSection(in, compressor, type);
+ } else if (type == CLASSES) {
+ readClassesContent(in, compressor);
+ } else {
+ readSectionBytes(in, compressor, type);
+ }
+ // ## appropriate exception ??
+ if (cs.read() != -1)
+ throw new IllegalArgumentException("All section content not read");
+
byte[] headerHash = header.getHashNoClone();
checkHashMatch(headerHash, sectionDigest.digest());
- if (header.getType() != SectionType.SIGNATURE) {
+ if (header.getType() != SIGNATURE) {
calculatedHashes.add(headerHash);
}
}
- public void readFile(DataInputStream in,
- Compressor compressor,
- SectionType type,
- int csize)
+ // module-info OR module signature
+ public void readSectionBytes(DataInputStream in, Compressor compressor,
+ SectionType type)
throws IOException
{
+ Decompressor decompressor = Decompressor.newInstance(in, compressor);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ decompressor.extractTo(baos);
+
+ if (type == MODULE_INFO)
+ moduleInfoBytes = baos.toByteArray();
+ else if (type == SIGNATURE) {
+ SignatureSection sh = SignatureSection.read(new DataInputStream(
+ new ByteArrayInputStream(baos.toByteArray())));
+ moduleSignatureType = sh.getSignatureType();
+ moduleSignatureBytes = sh.getSignature();
+ } else
+ throw new IllegalArgumentException(
+ "Unsupported raw bytes section type: " + type);
+ }
+
+ public void readClassesContent(DataInputStream in, Compressor compressor)
+ throws IOException
+ {
+ ClassesDecompressor decompressor =
+ ClassesDecompressor.newInstance(in, compressor, deflate);
+ decompressor.extractTo(contentStream());
+ }
+
+ // subsections/files (resources, libs, cmds, configs)
+ public void readSubSection(DataInputStream in, Compressor compressor,
+ SectionType type)
+ throws IOException
+ {
+ assert type == RESOURCES || type == NATIVE_LIBS ||
+ type == NATIVE_CMDS || type == CONFIG;
+
+ SubSectionFileHeader header = SubSectionFileHeader.read(in);
+ CountingInputStream cs = new CountingInputStream(in, header.getCSize());
+ Decompressor decompressor = Decompressor.newInstance(cs, compressor);
+ String path = header.getPath();
+ try (OutputStream sink = openOutputStream(type, path)) {
+ decompressor.extractTo(sink);
+ }
+
+ String prefix = type == RESOURCES ? "" :
+ getSubdirOfSection(type) + File.separator;
+ contents.add(prefix + path);
+ // post processing for executable and files outside the module dir
+ if (extract)
+ postExtract(type, currentPath);
+ }
+
+ static class Decompressor {
+ protected InputStream source;
+ protected Decompressor() { }
+ protected Decompressor(InputStream source) {
+ // no decompression
+ this.source = source;
+ }
+
+ void extractTo(OutputStream sink) throws IOException {
+ copyStream(source, sink);
+ }
+
+ static Decompressor newInstance(InputStream source,
+ Compressor compressor)
+ throws IOException
+ {
switch (compressor) {
case NONE:
- if (type == SectionType.MODULE_INFO) {
- moduleInfoBytes = readModuleInfo(in, csize);
+ return new Decompressor(source);
+ case GZIP:
+ return new GZIPDecompressor(source);
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported compressor type: " + compressor);
+ }
+ }
+ }
- } else if (type == SectionType.SIGNATURE) {
- // Examine the Signature header
- moduleSignatureType = (int)in.readShort();
- int length = in.readInt();
- moduleSignatureBytes = readModuleSignature(in, csize - 6);
- if (length != moduleSignatureBytes.length) {
- throw new IOException("Invalid Signature length");
+ static class GZIPDecompressor extends Decompressor {
+ GZIPDecompressor(InputStream source) throws IOException {
+ this.source = new GZIPInputStream(source) {
+ public void close() throws IOException {}
+ };
}
- } else {
- readUncompressedFile(in, type, csize);
}
- break;
- case GZIP:
- readGZIPCompressedFile(in, type);
- break;
+
+ static abstract class ClassesDecompressor {
+ protected InputStream source;
+
+ abstract void extractTo(JarOutputStream sink) throws IOException;
+
+ static ClassesDecompressor newInstance(InputStream source,
+ Compressor compressor,
+ boolean deflate)
+ throws IOException
+ {
+ switch (compressor) {
case PACK200_GZIP:
- readClasses(
- new DataInputStream(new CountingInputStream(in, csize)));
- break;
+ return new Pack200GZIPDecompressor(source, deflate);
default:
- throw new IOException("Unsupported Compressor for files: " +
- compressor);
+ throw new IllegalArgumentException(
+ "Unsupported compressor type: " + compressor);
}
}
+ }
- public void readClasses(DataInputStream in) throws IOException {
- unpack200gzip(in);
+ static class Pack200GZIPDecompressor extends ClassesDecompressor {
+ private Pack200.Unpacker unpacker;
+
+ Pack200GZIPDecompressor(InputStream source, boolean deflate)
+ throws IOException
+ {
+ this.source = new GZIPInputStream(source) {
+ public void close() throws IOException {}
+ };
+ unpacker = Pack200.newUnpacker();
+ if (deflate) {
+ Map<String,String> p = unpacker.properties();
+ p.put(Pack200.Unpacker.DEFLATE_HINT, Pack200.Unpacker.TRUE);
}
+ }
+ void extractTo(JarOutputStream sink) throws IOException {
+ unpacker.unpack(source, sink);
+ }
+ }
+
+ private TrackingJarOutputStream contentStream = null;
+
+ // Used to retrieved archive contents
+ private static class TrackingJarOutputStream extends JarOutputStream {
+ private final List<String> contents;
+ TrackingJarOutputStream(OutputStream out, List<String> contents)
+ throws IOException
+ {
+ super(out);
+ this.contents = contents;
+ }
+
+ public void putNextEntry(ZipEntry ze) throws IOException {
+ super.putNextEntry(ze);
+ contents.add(ze.getName());
+ }
+ }
+
+ private JarOutputStream contentStream() throws IOException {
+ if (contentStream != null)
+ return contentStream;
+
+ OutputStream sink;
+ if (extract)
+ sink = new BufferedOutputStream(
+ new FileOutputStream(computeRealPath("classes")));
+ else
+ sink = new NullOutputStream();
+
+ return contentStream = new TrackingJarOutputStream(sink, contents);
+ }
+
private File currentPath = null;
- private OutputStream openOutputStream(SectionType type,
- String path)
+ private OutputStream openOutputStream(SectionType type, String path)
throws IOException
{
if (!extract)
return new NullOutputStream();
+
currentPath = null;
- assert type != SectionType.CLASSES;
- if (type == SectionType.RESOURCES)
- return Files.newOutputStream(contentStream(), path);
+ assert type != CLASSES;
+ if (type == RESOURCES)
+ return Files.newOutputStream(contentStream(), deflate, path);
currentPath = computeRealPath(type, path);
File parent = currentPath.getParentFile();
if (!parent.exists())
Files.mkdirs(parent, currentPath.getName());
return new BufferedOutputStream(new FileOutputStream(currentPath));
@@ -429,76 +547,25 @@
public void write(byte[] b) throws IOException {}
@Override
public void write(byte[] b, int off, int len) throws IOException {}
}
- public void readGZIPCompressedFile(DataInputStream in,
- SectionType type)
+ private static void checkHashMatch(byte[] expected, byte[] computed)
throws IOException
{
- SubSectionFileHeader header = SubSectionFileHeader.read(in);
- int csize = header.getCSize();
-
- // Splice off the compressed file from input stream
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- copyStream(new CountingInputStream(in, csize), baos, csize);
-
- byte[] compressedfile = baos.toByteArray();
- ByteArrayInputStream bain
- = new ByteArrayInputStream(compressedfile);
- try (GZIPInputStream gin = new GZIPInputStream(bain);
- OutputStream out = openOutputStream(type, header.getPath())) {
- copyStream(gin, out);
+ if (!MessageDigest.isEqual(expected, computed))
+ throw new IOException("Expected hash "
+ + hashHexString(expected)
+ + " instead of "
+ + hashHexString(computed));
}
- if (extract)
- postExtract(type, currentPath);
- }
-
- public void readUncompressedFile(DataInputStream in,
- SectionType type,
- int csize)
- throws IOException
- {
- assert type != SectionType.MODULE_INFO;
- SubSectionFileHeader header = SubSectionFileHeader.read(in);
- csize = header.getCSize();
- try (OutputStream out = openOutputStream(type, header.getPath())) {
- CountingInputStream cin = new CountingInputStream(in, csize);
- byte[] buf = new byte[8192];
- int n;
- while ((n = cin.read(buf)) >= 0)
- out.write(buf, 0, n);
- }
- if (extract) {
- postExtract(type, currentPath);
- }
- }
-
- public byte[] readModuleInfo(DataInputStream in, int csize)
- throws IOException
- {
- CountingInputStream cin = new CountingInputStream(in, csize);
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- byte[] buf = new byte[8192];
- int n;
- while ((n = cin.read(buf)) >= 0)
- out.write(buf, 0, n);
- return out.toByteArray();
- }
-
- public byte[] readModuleSignature(DataInputStream in, int csize)
- throws IOException
- {
- return readModuleInfo(in, csize); // signature has the same format
- }
-
// Track files installed outside the module library. For later removal.
// files are relative to the modules directory.
private PrintWriter filesWriter;
- private void trackFiles(SectionType type, File file)
+ private void trackFiles(File file)
throws IOException
{
if (file == null || file.toPath().startsWith(destination.toPath()))
return;
@@ -541,15 +608,15 @@
return excs;
}
// Returns the absolute path of the given section type.
private File getDirOfSection(SectionType type) {
- if (type == SectionType.NATIVE_LIBS)
+ if (type == NATIVE_LIBS)
return natlibs;
- else if (type == SectionType.NATIVE_CMDS)
+ else if (type == NATIVE_CMDS)
return natcmds;
- else if (type == SectionType.CONFIG)
+ else if (type == CONFIG)
return configs;
// resolve sub dir section paths against the modules directory
return new File(destination, ModuleFile.getSubdirOfSection(type));
}
@@ -576,46 +643,28 @@
}
private static void markNativeCodeExecutable(SectionType type,
File file)
{
- if (type == SectionType.NATIVE_CMDS
- || (type == SectionType.NATIVE_LIBS
- && System.getProperty("os.name").startsWith("Windows")))
- {
+ if (type == NATIVE_CMDS || (type == NATIVE_LIBS &&
+ System.getProperty("os.name").startsWith("Windows")))
file.setExecutable(true);
}
- }
private void postExtract(SectionType type, File path)
throws IOException
{
markNativeCodeExecutable(type, path);
- trackFiles(type, path);
+ trackFiles(path);
}
-
- private void unpack200gzip(DataInputStream in) throws IOException {
- GZIPInputStream gis = new GZIPInputStream(in) {
- public void close() throws IOException {}
- };
- Pack200.Unpacker unpacker = Pack200.newUnpacker();
- if (deflate) {
- Map<String,String> p = unpacker.properties();
- p.put(Pack200.Unpacker.DEFLATE_HINT, Pack200.Unpacker.TRUE);
}
- unpacker.unpack(gis, contentStream());
- }
- }
-
private static void checkCompressor(SectionType type,
Compressor compressor) {
- if ((SectionType.MODULE_INFO == type &&
- Compressor.NONE != compressor)
- || (SectionType.CLASSES == type &&
- Compressor.PACK200_GZIP != compressor))
+ if ((MODULE_INFO == type && Compressor.NONE != compressor) ||
+ (CLASSES == type && Compressor.PACK200_GZIP != compressor))
throw new IllegalArgumentException(type
+ " may not use compressor "
+ compressor);
}
@@ -627,49 +676,25 @@
+ subsections);
else if (type.hasFiles() && subsections == 0)
throw new IllegalArgumentException(type + " subsection count is 0");
}
- private static void copyStream(InputStream in, DataOutput out)
+ private static void copyStream(InputStream source, DataOutput sink)
throws IOException
{
-
- byte[] buffer = new byte[1024 * 8];
- for (int b_read = in.read(buffer);
- -1 != b_read;
- b_read = in.read(buffer))
- out.write(buffer, 0, b_read);
+ byte[] buf = new byte[8192];
+ int b_read = 0;
+ while((b_read = source.read(buf)) > 0)
+ sink.write(buf, 0, b_read);
}
- private static void copyStream(InputStream in, OutputStream out)
+ private static void copyStream(InputStream source, OutputStream sink)
throws IOException
{
- copyStream(in, (DataOutput) new DataOutputStream(out));
+ copyStream(source, (DataOutput) new DataOutputStream(sink));
}
- private static void copyStream(InputStream in, DataOutput out,
- int count)
- throws IOException
- {
- byte[] buffer = new byte[1024 * 8];
-
- while(count > 0) {
- int b_read = in.read(buffer, 0, Math.min(count, buffer.length));
- if (-1 == b_read)
- return;
- out.write(buffer, 0, b_read);
- count-=b_read;
- }
- }
-
- private static void copyStream(InputStream in, OutputStream out,
- int count)
- throws IOException
- {
- copyStream(in, (DataOutput) new DataOutputStream(out), count);
- }
-
private static void ensureNonNegativity(long size, String parameter) {
if (size < 0)
throw new IllegalArgumentException(parameter + "<0: " + size);
}
@@ -784,11 +809,10 @@
}
private static byte[] readHashBytes(DataInputStream in, short hashLength)
throws IOException
{
-
final byte[] hash = new byte[hashLength];
in.readFully(hash);
return hash;
}
@@ -798,11 +822,10 @@
}
private static byte[] readFileHash(DigestInputStream dis)
throws IOException
{
-
DataInputStream in = new DataInputStream(dis);
final short hashLength = readHashLength(in);
// Turn digest computation off before reading the file hash
@@ -1052,10 +1075,56 @@
final String path = in.readUTF();
return new SubSectionFileHeader(csize, path);
}
}
+
+ public final static class SignatureSection {
+ private final int signatureType; // One of FileConstants.ModuleFile.HashType
+ private final int signatureLength; // Length of signature
+ private final byte[] signature; // Signature bytes
+
+ public int getSignatureType() {
+ return signatureType;
+ }
+
+ public int getSignatureLength() {
+ return signatureLength;
+ }
+
+ public byte[] getSignature() {
+ return signature;
+ }
+
+ public SignatureSection(int signatureType, int signatureLength,
+ byte[] signature) {
+ ensureNonNegativity(signatureLength, "signatureLength");
+
+ this.signatureType = signatureType;
+ this.signatureLength = signatureLength;
+ this.signature = signature.clone();
+ }
+
+ public void write(DataOutput out) throws IOException {
+ out.writeShort(signatureType);
+ out.writeInt(signatureLength);
+ out.write(signature);
+ }
+
+ public static SignatureSection read(DataInputStream in)
+ throws IOException
+ {
+ final short signatureType = in.readShort();
+ ensureMatch(signatureType, SignatureType.PKCS7.value(), "SignatureType.PKCS7");
+ final int signatureLength = in.readInt();
+ ensureNonNegativity(signatureLength, "signatureLength");
+ final byte[] signature = new byte[signatureLength];
+ in.readFully(signature);
+ return new SignatureSection(signatureType, signatureLength,
+ signature);
+ }
+ }
private static void writeHash(DataOutput out, byte[] hash)
throws IOException
{
out.writeShort(hash.length);