|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
package jdk.internal.jimage; |
|
|
|
import java.io.ByteArrayInputStream; |
|
import java.io.IOException; |
|
import java.io.InputStream; |
|
import java.lang.reflect.InvocationTargetException; |
|
import java.lang.reflect.Method; |
|
import java.nio.ByteBuffer; |
|
import java.nio.ByteOrder; |
|
import java.nio.IntBuffer; |
|
import java.nio.channels.FileChannel; |
|
import java.nio.file.Path; |
|
import java.nio.file.StandardOpenOption; |
|
import java.security.AccessController; |
|
import java.security.PrivilegedAction; |
|
import java.util.Objects; |
|
import java.util.stream.IntStream; |
|
import jdk.internal.jimage.decompressor.Decompressor; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public class BasicImageReader implements AutoCloseable { |
|
private static boolean isSystemProperty(String key, String value, String def) { |
|
|
|
return AccessController.doPrivileged( |
|
new PrivilegedAction<Boolean>() { |
|
@Override |
|
public Boolean run() { |
|
return value.equals(System.getProperty(key, def)); |
|
} |
|
}); |
|
} |
|
|
|
static private final boolean IS_64_BIT = |
|
isSystemProperty("sun.arch.data.model", "64", "32"); |
|
static private final boolean USE_JVM_MAP = |
|
isSystemProperty("jdk.image.use.jvm.map", "true", "true"); |
|
static private final boolean MAP_ALL = |
|
isSystemProperty("jdk.image.map.all", "true", IS_64_BIT ? "true" : "false"); |
|
|
|
private final Path imagePath; |
|
private final ByteOrder byteOrder; |
|
private final String name; |
|
private final ByteBuffer memoryMap; |
|
private final FileChannel channel; |
|
private final ImageHeader header; |
|
private final long indexSize; |
|
private final IntBuffer redirect; |
|
private final IntBuffer offsets; |
|
private final ByteBuffer locations; |
|
private final ByteBuffer strings; |
|
private final ImageStringsReader stringsReader; |
|
private final Decompressor decompressor; |
|
|
|
protected BasicImageReader(Path path, ByteOrder byteOrder) |
|
throws IOException { |
|
this.imagePath = Objects.requireNonNull(path); |
|
this.byteOrder = Objects.requireNonNull(byteOrder); |
|
this.name = this.imagePath.toString(); |
|
|
|
ByteBuffer map; |
|
|
|
if (USE_JVM_MAP && BasicImageReader.class.getClassLoader() == null) { |
|
// Check to see if the jvm has opened the file using libjimage |
|
|
|
map = NativeImageBuffer.getNativeMap(name); |
|
} else { |
|
map = null; |
|
} |
|
|
|
|
|
if (map != null && MAP_ALL) { |
|
channel = null; |
|
} else { |
|
channel = FileChannel.open(imagePath, StandardOpenOption.READ); |
|
|
|
AccessController.doPrivileged(new PrivilegedAction<Void>() { |
|
@Override |
|
public Void run() { |
|
if (BasicImageReader.class.getClassLoader() == null) { |
|
try { |
|
Class<?> fileChannelImpl = |
|
Class.forName("sun.nio.ch.FileChannelImpl"); |
|
Method setUninterruptible = |
|
fileChannelImpl.getMethod("setUninterruptible"); |
|
setUninterruptible.invoke(channel); |
|
} catch (ClassNotFoundException | |
|
NoSuchMethodException | |
|
IllegalAccessException | |
|
InvocationTargetException ex) { |
|
// fall thru - will only happen on JDK-8 systems where this code |
|
// is only used by tools using jrt-fs (non-critical.) |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
}); |
|
} |
|
|
|
|
|
if (MAP_ALL && map == null) { |
|
map = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); |
|
} |
|
|
|
|
|
ByteBuffer headerBuffer = map; |
|
int headerSize = ImageHeader.getHeaderSize(); |
|
|
|
|
|
if (headerBuffer == null) { |
|
headerBuffer = ByteBuffer.allocateDirect(headerSize); |
|
if (channel.read(headerBuffer, 0L) == headerSize) { |
|
headerBuffer.rewind(); |
|
} else { |
|
throw new IOException("\"" + name + "\" is not an image file"); |
|
} |
|
} else if (headerBuffer.capacity() < headerSize) { |
|
throw new IOException("\"" + name + "\" is not an image file"); |
|
} |
|
|
|
|
|
header = readHeader(intBuffer(headerBuffer, 0, headerSize)); |
|
indexSize = header.getIndexSize(); |
|
|
|
|
|
if (map == null) { |
|
|
|
map = channel.map(FileChannel.MapMode.READ_ONLY, 0, indexSize); |
|
} |
|
|
|
memoryMap = map.asReadOnlyBuffer(); |
|
|
|
|
|
if (memoryMap.capacity() < indexSize) { |
|
throw new IOException("The image file \"" + name + "\" is corrupted"); |
|
} |
|
redirect = intBuffer(memoryMap, header.getRedirectOffset(), header.getRedirectSize()); |
|
offsets = intBuffer(memoryMap, header.getOffsetsOffset(), header.getOffsetsSize()); |
|
locations = slice(memoryMap, header.getLocationsOffset(), header.getLocationsSize()); |
|
strings = slice(memoryMap, header.getStringsOffset(), header.getStringsSize()); |
|
|
|
stringsReader = new ImageStringsReader(this); |
|
decompressor = new Decompressor(); |
|
} |
|
|
|
protected BasicImageReader(Path imagePath) throws IOException { |
|
this(imagePath, ByteOrder.nativeOrder()); |
|
} |
|
|
|
public static BasicImageReader open(Path imagePath) throws IOException { |
|
return new BasicImageReader(imagePath, ByteOrder.nativeOrder()); |
|
} |
|
|
|
public ImageHeader getHeader() { |
|
return header; |
|
} |
|
|
|
private ImageHeader readHeader(IntBuffer buffer) throws IOException { |
|
ImageHeader result = ImageHeader.readFrom(buffer); |
|
|
|
if (result.getMagic() != ImageHeader.MAGIC) { |
|
throw new IOException("\"" + name + "\" is not an image file"); |
|
} |
|
|
|
if (result.getMajorVersion() != ImageHeader.MAJOR_VERSION || |
|
result.getMinorVersion() != ImageHeader.MINOR_VERSION) { |
|
throw new IOException("The image file \"" + name + "\" is not " + |
|
"the correct version. Major: " + result.getMajorVersion() + |
|
". Minor: " + result.getMinorVersion()); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
private static ByteBuffer slice(ByteBuffer buffer, int position, int capacity) { |
|
// Note that this is the only limit and position manipulation of |
|
// BasicImageReader private ByteBuffers. The synchronize could be avoided |
|
// by cloning the buffer to make a local copy, but at the cost of creating |
|
|
|
synchronized(buffer) { |
|
buffer.limit(position + capacity); |
|
buffer.position(position); |
|
return buffer.slice(); |
|
} |
|
} |
|
|
|
private IntBuffer intBuffer(ByteBuffer buffer, int offset, int size) { |
|
return slice(buffer, offset, size).order(byteOrder).asIntBuffer(); |
|
} |
|
|
|
public static void releaseByteBuffer(ByteBuffer buffer) { |
|
Objects.requireNonNull(buffer); |
|
|
|
if (!MAP_ALL) { |
|
ImageBufferCache.releaseBuffer(buffer); |
|
} |
|
} |
|
|
|
public String getName() { |
|
return name; |
|
} |
|
|
|
public ByteOrder getByteOrder() { |
|
return byteOrder; |
|
} |
|
|
|
public Path getImagePath() { |
|
return imagePath; |
|
} |
|
|
|
@Override |
|
public void close() throws IOException { |
|
if (channel != null) { |
|
channel.close(); |
|
} |
|
} |
|
|
|
public ImageStringsReader getStrings() { |
|
return stringsReader; |
|
} |
|
|
|
public synchronized ImageLocation findLocation(String module, String name) { |
|
Objects.requireNonNull(module); |
|
Objects.requireNonNull(name); |
|
// Details of the algorithm used here can be found in |
|
|
|
int count = header.getTableLength(); |
|
int index = redirect.get(ImageStringsReader.hashCode(module, name) % count); |
|
|
|
if (index < 0) { |
|
|
|
index = -index - 1; |
|
} else if (index > 0) { |
|
|
|
index = ImageStringsReader.hashCode(module, name, index) % count; |
|
} else { |
|
|
|
return null; |
|
} |
|
|
|
long[] attributes = getAttributes(offsets.get(index)); |
|
|
|
if (!ImageLocation.verify(module, name, attributes, stringsReader)) { |
|
return null; |
|
} |
|
return new ImageLocation(attributes, stringsReader); |
|
} |
|
|
|
public synchronized ImageLocation findLocation(String name) { |
|
Objects.requireNonNull(name); |
|
// Details of the algorithm used here can be found in |
|
|
|
int count = header.getTableLength(); |
|
int index = redirect.get(ImageStringsReader.hashCode(name) % count); |
|
|
|
if (index < 0) { |
|
|
|
index = -index - 1; |
|
} else if (index > 0) { |
|
|
|
index = ImageStringsReader.hashCode(name, index) % count; |
|
} else { |
|
|
|
return null; |
|
} |
|
|
|
long[] attributes = getAttributes(offsets.get(index)); |
|
|
|
if (!ImageLocation.verify(name, attributes, stringsReader)) { |
|
return null; |
|
} |
|
return new ImageLocation(attributes, stringsReader); |
|
} |
|
|
|
public String[] getEntryNames() { |
|
int[] attributeOffsets = new int[offsets.capacity()]; |
|
offsets.get(attributeOffsets); |
|
return IntStream.of(attributeOffsets) |
|
.filter(o -> o != 0) |
|
.mapToObj(o -> ImageLocation.readFrom(this, o).getFullName()) |
|
.sorted() |
|
.toArray(String[]::new); |
|
} |
|
|
|
ImageLocation getLocation(int offset) { |
|
return ImageLocation.readFrom(this, offset); |
|
} |
|
|
|
public long[] getAttributes(int offset) { |
|
if (offset < 0 || offset >= locations.limit()) { |
|
throw new IndexOutOfBoundsException("offset"); |
|
} |
|
|
|
ByteBuffer buffer = slice(locations, offset, locations.limit() - offset); |
|
return ImageLocation.decompress(buffer); |
|
} |
|
|
|
public String getString(int offset) { |
|
if (offset < 0 || offset >= strings.limit()) { |
|
throw new IndexOutOfBoundsException("offset"); |
|
} |
|
|
|
ByteBuffer buffer = slice(strings, offset, strings.limit() - offset); |
|
return ImageStringsReader.stringFromByteBuffer(buffer); |
|
} |
|
|
|
private byte[] getBufferBytes(ByteBuffer buffer) { |
|
Objects.requireNonNull(buffer); |
|
byte[] bytes = new byte[buffer.limit()]; |
|
buffer.get(bytes); |
|
|
|
return bytes; |
|
} |
|
|
|
private ByteBuffer readBuffer(long offset, long size) { |
|
if (offset < 0 || Integer.MAX_VALUE <= offset) { |
|
throw new IndexOutOfBoundsException("Bad offset: " + offset); |
|
} |
|
|
|
if (size < 0 || Integer.MAX_VALUE <= size) { |
|
throw new IndexOutOfBoundsException("Bad size: " + size); |
|
} |
|
|
|
if (MAP_ALL) { |
|
ByteBuffer buffer = slice(memoryMap, (int)offset, (int)size); |
|
buffer.order(ByteOrder.BIG_ENDIAN); |
|
|
|
return buffer; |
|
} else { |
|
if (channel == null) { |
|
throw new InternalError("Image file channel not open"); |
|
} |
|
|
|
ByteBuffer buffer = ImageBufferCache.getBuffer(size); |
|
int read; |
|
try { |
|
read = channel.read(buffer, offset); |
|
buffer.rewind(); |
|
} catch (IOException ex) { |
|
ImageBufferCache.releaseBuffer(buffer); |
|
throw new RuntimeException(ex); |
|
} |
|
|
|
if (read != size) { |
|
ImageBufferCache.releaseBuffer(buffer); |
|
throw new RuntimeException("Short read: " + read + |
|
" instead of " + size + " bytes"); |
|
} |
|
|
|
return buffer; |
|
} |
|
} |
|
|
|
public byte[] getResource(String name) { |
|
Objects.requireNonNull(name); |
|
ImageLocation location = findLocation(name); |
|
|
|
return location != null ? getResource(location) : null; |
|
} |
|
|
|
public byte[] getResource(ImageLocation loc) { |
|
ByteBuffer buffer = getResourceBuffer(loc); |
|
|
|
if (buffer != null) { |
|
byte[] bytes = getBufferBytes(buffer); |
|
ImageBufferCache.releaseBuffer(buffer); |
|
|
|
return bytes; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
public ByteBuffer getResourceBuffer(ImageLocation loc) { |
|
Objects.requireNonNull(loc); |
|
long offset = loc.getContentOffset() + indexSize; |
|
long compressedSize = loc.getCompressedSize(); |
|
long uncompressedSize = loc.getUncompressedSize(); |
|
|
|
if (compressedSize < 0 || Integer.MAX_VALUE < compressedSize) { |
|
throw new IndexOutOfBoundsException( |
|
"Bad compressed size: " + compressedSize); |
|
} |
|
|
|
if (uncompressedSize < 0 || Integer.MAX_VALUE < uncompressedSize) { |
|
throw new IndexOutOfBoundsException( |
|
"Bad uncompressed size: " + uncompressedSize); |
|
} |
|
|
|
if (compressedSize == 0) { |
|
return readBuffer(offset, uncompressedSize); |
|
} else { |
|
ByteBuffer buffer = readBuffer(offset, compressedSize); |
|
|
|
if (buffer != null) { |
|
byte[] bytesIn = getBufferBytes(buffer); |
|
ImageBufferCache.releaseBuffer(buffer); |
|
byte[] bytesOut; |
|
|
|
try { |
|
bytesOut = decompressor.decompressResource(byteOrder, |
|
(int strOffset) -> getString(strOffset), bytesIn); |
|
} catch (IOException ex) { |
|
throw new RuntimeException(ex); |
|
} |
|
|
|
return ByteBuffer.wrap(bytesOut); |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
public InputStream getResourceStream(ImageLocation loc) { |
|
Objects.requireNonNull(loc); |
|
byte[] bytes = getResource(loc); |
|
|
|
return new ByteArrayInputStream(bytes); |
|
} |
|
} |