src/share/classes/org/openjdk/jigsaw/ModuleFile.java
Print this page
@@ -28,13 +28,14 @@
import java.io.*;
import java.nio.charset.Charset;
import java.security.*;
import java.util.*;
import java.util.jar.*;
-import java.util.zip.*;
-
+import org.openjdk.jigsaw.ModuleFileParser.Event;
import static org.openjdk.jigsaw.FileConstants.ModuleFile.*;
+import static org.openjdk.jigsaw.FileConstants.ModuleFile.SectionType.*;
+import static org.openjdk.jigsaw.ModuleFileParser.Event.*;
public final class ModuleFile {
/**
* Return the subdir of a section in an extracted module file.
*/
@@ -55,456 +56,251 @@
default:
throw new AssertionError(type);
}
}
+ /**
+ * Returns a ModuleFileParser instance.
+ *
+ * @param stream
+ * module file stream
+ *
+ * @return a module file parser
+ *
+ * @throws ModuleFileParserException
+ * If there is an error processing the underlying module file
+ */
+ public static ModuleFileParser newParser(InputStream stream) {
+ return new ModuleFileParserImpl(stream);
+ }
+
public final static class Reader implements Closeable {
+ private final ModuleFileParser parser;
+ private final ModuleFileHeader fileHeader;
+ private final byte[] moduleInfoBytes;
+ private final SignatureType moduleSignatureType;
+ private final byte[] moduleSignatureBytes ;
+ private final List<byte[]> calculatedHashes;
- private DataInputStream stream;
private File destination;
private boolean deflate;
- private final HashType hashtype = HashType.SHA256;
private File natlibs;
private File natcmds;
private File configs;
- private static class CountingInputStream extends FilterInputStream {
- int count;
- public CountingInputStream(InputStream stream, int count) {
- super(stream);
- this.count = count;
- }
+ public Reader(InputStream stream) throws IOException {
+ calculatedHashes = new ArrayList<>();
+ parser = ModuleFile.newParser(stream);
+ fileHeader = parser.fileHeader();
+ calculatedHashes.add(parser.getHash());
+ // Read the MODULE_INFO and the Signature section (if present),
+ // but does not write any files.
+ parser.next();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ copyStream(parser.getRawStream(), baos);
+ moduleInfoBytes = baos.toByteArray();
+ assert moduleInfoBytes != null;
- public int available() throws IOException {
- return count;
- }
+ if (parser.next() != END_SECTION)
+ throw new ModuleFileParserException(
+ "Expected END_SECTION of module-info");
+ calculatedHashes.add(parser.getHash());
- public boolean markSupported() {
- return false;
+ if (parser.hasNext()) {
+ Event event = parser.next();
+ if (event != END_FILE) { // more sections
+ SectionHeader header = parser.getSectionHeader();
+ if (header.type == SIGNATURE) {
+ SignatureSection sh = SignatureSection.read(
+ new DataInputStream(parser.getRawStream()));
+ moduleSignatureType = SignatureType.valueOf(sh.getSignatureType());
+ moduleSignatureBytes = sh.getSignature();
+ if (parser.next() != END_SECTION)
+ throw new ModuleFileParserException(
+ "Expected END_SECTION of signature");
+ if (parser.hasNext())
+ parser.next(); // position parser at next event
+ return;
}
-
- public int read() throws IOException {
- if (count == 0)
- return -1;
- int read = super.read();
- if (-1 != read)
- count--;
- return read;
}
-
- public int read(byte[] b, int off, int len) throws IOException {
- if (count == 0)
- return -1;
- len = Math.min(len, count);
- int read = super.read(b, off, len);
- if (-1 != read)
- count-=read;
- return read;
}
-
- public void reset() throws IOException {
- throw new IOException("Can't reset this stream");
+ // no signature section, or possibly other sections at all.
+ moduleSignatureBytes = null;
+ moduleSignatureType = null;
}
- public long skip(long n) throws IOException {
- if (count == 0)
- return -1;
- n = Math.min(n, count);
- long skipped = super.skip(n);
- if (n > 0)
- count-=skipped;
- return skipped;
+ public void extractTo(File dst) throws IOException {
+ extractTo(dst, false);
}
- }
- public Reader(DataInputStream stream) {
- // Ensure that mark/reset is supported
- if (stream.markSupported()) {
- this.stream = stream;
- } else {
- this.stream =
- new DataInputStream(new BufferedInputStream(stream));
+ public void extractTo(File dst, boolean deflate) throws IOException {
+ extractTo(dst, deflate, null, null, null);
}
- }
- 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;
- private SignatureType moduleSignatureType = null;
- private byte[] moduleSignatureBytes = null;
- private final int MAX_SECTION_HEADER_LENGTH = 128;
- private List<byte[]> calculatedHashes = new ArrayList<>();
- private boolean extract = true;
-
- /*
- * Reads the MODULE_INFO section and the Signature section, if present,
- * but does not write any files.
- */
- public byte[] readStart() throws IOException {
-
- try {
- fileDigest = getHashInstance(hashtype);
- sectionDigest = getHashInstance(hashtype);
- DigestInputStream dis =
- new DigestInputStream(stream, fileDigest);
- fileHeader = ModuleFileHeader.read(dis);
- // calculate module header hash
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- fileHeader.write(new DataOutputStream(baos));
- sectionDigest.update(baos.toByteArray());
- calculatedHashes.add(sectionDigest.digest());
-
- fileIn = new DataInputStream(dis);
- if (readSection(fileIn) != SectionType.MODULE_INFO)
- throw new IOException("First module-file section"
- + " is not MODULE_INFO");
- assert moduleInfoBytes != null;
-
- // Read the Signature Section, if present
- readSignatureSection(fileIn, dis);
-
- return moduleInfoBytes.clone();
- } catch (IOException x) {
- close();
- throw x;
- }
- }
-
- public void readRest() throws IOException {
- extract = false;
- readRest(null, false, null, null, null);
- }
-
- public void readRest(File dst, boolean deflate) throws IOException {
- readRest(dst, deflate, null, null, null);
- }
-
- public void readRest(File dst, boolean deflate, File natlibs,
+ public void extractTo(File dst, boolean deflate, File natlibs,
File natcmds, File configs)
throws IOException
{
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");
+
try {
- if (extract)
Files.store(moduleInfoBytes, computeRealPath("info"));
- // 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()) {
- stream.reset();
- readSection(fileIn);
- stream.mark(1);
+ Event event = parser.event();
+ if (event == END_FILE)
+ return;
+
+ if (event != START_SECTION)
+ throw new ModuleFileParserException(
+ "Expected START_SECTION, got : " + event);
+ // Module-Info and Signature, if present, have been consumed
+ do {
+ SectionHeader header = parser.getSectionHeader();
+ SectionType type = header.getType();
+ if (type.hasFiles()) {
+ while(parser.skipToNextStartSubSection()) {
+ readSubSection(type);
}
+ } else if (type == CLASSES) {
+ Iterator<Map.Entry<String,InputStream>> classes =
+ parser.getClasses();
+ while (classes.hasNext()) {
+ Map.Entry<String,InputStream> entry = classes.next();
+ try (OutputStream out = openOutputStream(type, entry.getKey())) {
+ copyStream(entry.getValue(), out);
+ }
+ }
+ // END_SECTION
+ parser.next();
+ } else {
+ throw new IllegalArgumentException("Unknown type: " + type);
+ }
+ byte[] sectionHash = parser.getHash();
+ calculatedHashes.add(sectionHash);
+ checkHashMatch(sectionHash, header.getHash());
+ } while (parser.skipToNextStartSection());
- close();
+ if (parser.event() != END_FILE)
+ throw new IOException("Expected END_FILE");
byte[] fileHeaderHash = fileHeader.getHashNoClone();
- checkHashMatch(fileHeaderHash, fileDigest.digest());
+ checkHashMatch(fileHeaderHash, parser.getHash());
calculatedHashes.add(fileHeaderHash);
} finally {
close();
}
}
- public byte[] getHash() throws IOException {
- if (null == fileHeader)
- readStart();
+ public byte[] getModuleInfoBytes() {
+ return moduleInfoBytes.clone();
+ }
+
+ public byte[] getHash() {
return fileHeader.getHash();
}
public List<byte[]> getCalculatedHashes() {
return calculatedHashes;
}
- public boolean hasSignature() throws IOException {
- if (null == fileHeader)
- readStart();
+ public boolean hasSignature() {
return moduleSignatureBytes != null;
}
- public SignatureType getSignatureType() throws IOException {
- if (null == fileHeader)
- readStart();
+ public SignatureType getSignatureType() {
return moduleSignatureType;
}
- public byte[] getSignature() throws IOException {
- if (null == fileHeader)
- readStart();
- return moduleSignatureBytes != null
- ? moduleSignatureBytes.clone()
- : null;
+ public byte[] getSignature() {
+ return moduleSignatureBytes == null ? null :
+ moduleSignatureBytes.clone();
}
- byte[] getSignatureNoClone() {
+ /*package*/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());
- }
- }
- return contentStream;
- }
-
public void close() throws IOException {
try {
try {
if (contentStream != null) {
contentStream.close();
contentStream = null;
}
} finally {
- if (fileIn != null) {
- fileIn.close();
- fileIn = null;
+ /*if (parser != null) {
+ parser.close();
+ parser = null;
+ }*/
}
- }
} finally {
if (filesWriter != null) {
filesWriter.close();
filesWriter = null;
}
}
}
- public void readModule() throws IOException {
- extract = false;
- readStart();
- readRest();
- }
+ // subsections/files (resources, libs, cmds, configs)
+ public void readSubSection(SectionType type) throws IOException {
+ assert type == RESOURCES || type == NATIVE_LIBS ||
+ type == NATIVE_CMDS || type == CONFIG;
- public void readModule(File dst) throws IOException {
- readStart();
- readRest(dst, false);
+ SubSectionFileHeader subHeader = parser.getSubSectionFileHeader();
+ String path = subHeader.getPath();
+ try (OutputStream sink = openOutputStream(type, path)) {
+ copyStream(parser.getContentStream(), sink);
}
- 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) {
- readSectionContent(header, stream);
- } else {
- // Revert back to the starting position
- stream.reset();
+ // post processing for executable and files outside the module dir
+ postExtract(type, currentPath);
}
- }
- // Turn on digest computation again
- dis.on(true);
- }
+ private JarOutputStream contentStream = null;
- private SectionType readSection(DataInputStream stream)
- throws IOException
- {
- SectionHeader header = SectionHeader.read(stream);
- readSectionContent(header, stream);
- return header.getType();
- }
+ private JarOutputStream contentStream() throws IOException {
+ if (contentStream != null)
+ return contentStream;
- private void readSectionContent(SectionHeader header,
- DataInputStream stream)
- 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);
-
- for (int subsection = 0; subsection < subsections; subsection++)
- readFile(in, compressor, type, csize);
-
- byte[] headerHash = header.getHashNoClone();
- checkHashMatch(headerHash, sectionDigest.digest());
- if (header.getType() != SectionType.SIGNATURE) {
- calculatedHashes.add(headerHash);
+ return contentStream = new JarOutputStream(
+ new BufferedOutputStream(
+ new FileOutputStream(computeRealPath("classes"))));
}
- }
- public void readFile(DataInputStream in,
- Compressor compressor,
- SectionType type,
- int csize)
- throws IOException
- {
- switch (compressor) {
- case NONE:
- if (type == SectionType.MODULE_INFO) {
- moduleInfoBytes = readModuleInfo(in, csize);
-
- } else if (type == SectionType.SIGNATURE) {
- // Examine the Signature header
- int signatureTypeValue = (int)in.readShort();
- try {
- moduleSignatureType =
- SignatureType.valueOf(signatureTypeValue);
- } catch (IllegalArgumentException x) {
- throw new IOException("Invalid signature type: " +
- signatureTypeValue);
- }
- int length = in.readInt();
- moduleSignatureBytes = readModuleSignature(in, csize - 6);
- if (length != moduleSignatureBytes.length) {
- throw new IOException("Invalid Signature length");
- }
- } else {
- readUncompressedFile(in, type, csize);
- }
- break;
- case GZIP:
- readGZIPCompressedFile(in, type);
- break;
- case PACK200_GZIP:
- readClasses(
- new DataInputStream(new CountingInputStream(in, csize)));
- break;
- default:
- throw new IOException("Unsupported Compressor for files: " +
- compressor);
- }
- }
-
- public void readClasses(DataInputStream in) throws IOException {
- unpack200gzip(in);
- }
-
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);
+ if (type == CLASSES || 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));
}
- private static class NullOutputStream extends OutputStream {
- @Override
- public void write(int b) throws IOException {}
- @Override
- 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;
@@ -547,15 +343,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));
}
@@ -582,46 +378,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);
}
@@ -633,49 +411,19 @@
+ subsections);
else if (type.hasFiles() && subsections == 0)
throw new IllegalArgumentException(type + " subsection count is 0");
}
- private static void copyStream(InputStream in, DataOutput out)
- 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);
- }
-
private static void copyStream(InputStream in, OutputStream out)
throws IOException
{
- copyStream(in, (DataOutput) new DataOutputStream(out));
+ byte[] buf = new byte[8192];
+ int read;
+ while ((read = in.read(buf)) > 0)
+ out.write(buf, 0, read);
}
- 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);
}
@@ -802,11 +550,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
@@ -1054,10 +801,60 @@
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
+ {
+ short signatureType = in.readShort();
+ try {
+ SignatureType.valueOf(signatureType);
+ } catch (IllegalArgumentException x) {
+ throw new IOException("Invalid signature type: " + signatureType);
+ }
+ 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);