Path: blob/master/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java
41159 views
/*1* Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package jdk.internal.jimage;2526import java.io.ByteArrayInputStream;27import java.io.IOException;28import java.io.InputStream;29import java.lang.reflect.InvocationTargetException;30import java.lang.reflect.Method;31import java.nio.ByteBuffer;32import java.nio.ByteOrder;33import java.nio.IntBuffer;34import java.nio.channels.FileChannel;35import java.nio.file.Path;36import java.nio.file.StandardOpenOption;37import java.security.AccessController;38import java.security.PrivilegedAction;39import java.util.Objects;40import java.util.stream.IntStream;41import jdk.internal.jimage.decompressor.Decompressor;4243/**44* @implNote This class needs to maintain JDK 8 source compatibility.45*46* It is used internally in the JDK to implement jimage/jrtfs access,47* but also compiled and delivered as part of the jrtfs.jar to support access48* to the jimage file provided by the shipped JDK by tools running on JDK 8.49*/50public class BasicImageReader implements AutoCloseable {51@SuppressWarnings("removal")52private static boolean isSystemProperty(String key, String value, String def) {53// No lambdas during bootstrap54return AccessController.doPrivileged(55new PrivilegedAction<Boolean>() {56@Override57public Boolean run() {58return value.equals(System.getProperty(key, def));59}60});61}6263static private final boolean IS_64_BIT =64isSystemProperty("sun.arch.data.model", "64", "32");65static private final boolean USE_JVM_MAP =66isSystemProperty("jdk.image.use.jvm.map", "true", "true");67static private final boolean MAP_ALL =68isSystemProperty("jdk.image.map.all", "true", IS_64_BIT ? "true" : "false");6970private final Path imagePath;71private final ByteOrder byteOrder;72private final String name;73private final ByteBuffer memoryMap;74private final FileChannel channel;75private final ImageHeader header;76private final long indexSize;77private final IntBuffer redirect;78private final IntBuffer offsets;79private final ByteBuffer locations;80private final ByteBuffer strings;81private final ImageStringsReader stringsReader;82private final Decompressor decompressor;8384@SuppressWarnings("removal")85protected BasicImageReader(Path path, ByteOrder byteOrder)86throws IOException {87this.imagePath = Objects.requireNonNull(path);88this.byteOrder = Objects.requireNonNull(byteOrder);89this.name = this.imagePath.toString();9091ByteBuffer map;9293if (USE_JVM_MAP && BasicImageReader.class.getClassLoader() == null) {94// Check to see if the jvm has opened the file using libjimage95// native entry when loading the image for this runtime96map = NativeImageBuffer.getNativeMap(name);97} else {98map = null;99}100101// Open the file only if no memory map yet or is 32 bit jvm102if (map != null && MAP_ALL) {103channel = null;104} else {105channel = FileChannel.open(imagePath, StandardOpenOption.READ);106// No lambdas during bootstrap107AccessController.doPrivileged(new PrivilegedAction<Void>() {108@Override109public Void run() {110if (BasicImageReader.class.getClassLoader() == null) {111try {112Class<?> fileChannelImpl =113Class.forName("sun.nio.ch.FileChannelImpl");114Method setUninterruptible =115fileChannelImpl.getMethod("setUninterruptible");116setUninterruptible.invoke(channel);117} catch (ClassNotFoundException |118NoSuchMethodException |119IllegalAccessException |120InvocationTargetException ex) {121// fall thru - will only happen on JDK-8 systems where this code122// is only used by tools using jrt-fs (non-critical.)123}124}125126return null;127}128});129}130131// If no memory map yet and 64 bit jvm then memory map entire file132if (MAP_ALL && map == null) {133map = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());134}135136// Assume we have a memory map to read image file header137ByteBuffer headerBuffer = map;138int headerSize = ImageHeader.getHeaderSize();139140// If no memory map then read header from image file141if (headerBuffer == null) {142headerBuffer = ByteBuffer.allocateDirect(headerSize);143if (channel.read(headerBuffer, 0L) == headerSize) {144headerBuffer.rewind();145} else {146throw new IOException("\"" + name + "\" is not an image file");147}148} else if (headerBuffer.capacity() < headerSize) {149throw new IOException("\"" + name + "\" is not an image file");150}151152// Interpret the image file header153header = readHeader(intBuffer(headerBuffer, 0, headerSize));154indexSize = header.getIndexSize();155156// If no memory map yet then must be 32 bit jvm not previously mapped157if (map == null) {158// Just map the image index159map = channel.map(FileChannel.MapMode.READ_ONLY, 0, indexSize);160}161162memoryMap = map.asReadOnlyBuffer();163164// Interpret the image index165if (memoryMap.capacity() < indexSize) {166throw new IOException("The image file \"" + name + "\" is corrupted");167}168redirect = intBuffer(memoryMap, header.getRedirectOffset(), header.getRedirectSize());169offsets = intBuffer(memoryMap, header.getOffsetsOffset(), header.getOffsetsSize());170locations = slice(memoryMap, header.getLocationsOffset(), header.getLocationsSize());171strings = slice(memoryMap, header.getStringsOffset(), header.getStringsSize());172173stringsReader = new ImageStringsReader(this);174decompressor = new Decompressor();175}176177protected BasicImageReader(Path imagePath) throws IOException {178this(imagePath, ByteOrder.nativeOrder());179}180181public static BasicImageReader open(Path imagePath) throws IOException {182return new BasicImageReader(imagePath, ByteOrder.nativeOrder());183}184185public ImageHeader getHeader() {186return header;187}188189private ImageHeader readHeader(IntBuffer buffer) throws IOException {190ImageHeader result = ImageHeader.readFrom(buffer);191192if (result.getMagic() != ImageHeader.MAGIC) {193throw new IOException("\"" + name + "\" is not an image file");194}195196if (result.getMajorVersion() != ImageHeader.MAJOR_VERSION ||197result.getMinorVersion() != ImageHeader.MINOR_VERSION) {198throw new IOException("The image file \"" + name + "\" is not " +199"the correct version. Major: " + result.getMajorVersion() +200". Minor: " + result.getMinorVersion());201}202203return result;204}205206private static ByteBuffer slice(ByteBuffer buffer, int position, int capacity) {207// Note that this is the only limit and position manipulation of208// BasicImageReader private ByteBuffers. The synchronize could be avoided209// by cloning the buffer to make a local copy, but at the cost of creating210// a new object.211synchronized(buffer) {212buffer.limit(position + capacity);213buffer.position(position);214return buffer.slice();215}216}217218private IntBuffer intBuffer(ByteBuffer buffer, int offset, int size) {219return slice(buffer, offset, size).order(byteOrder).asIntBuffer();220}221222public static void releaseByteBuffer(ByteBuffer buffer) {223Objects.requireNonNull(buffer);224225if (!MAP_ALL) {226ImageBufferCache.releaseBuffer(buffer);227}228}229230public String getName() {231return name;232}233234public ByteOrder getByteOrder() {235return byteOrder;236}237238public Path getImagePath() {239return imagePath;240}241242@Override243public void close() throws IOException {244if (channel != null) {245channel.close();246}247}248249public ImageStringsReader getStrings() {250return stringsReader;251}252253public ImageLocation findLocation(String module, String name) {254int index = getLocationIndex(module, name);255if (index < 0) {256return null;257}258long[] attributes = getAttributes(offsets.get(index));259if (!ImageLocation.verify(module, name, attributes, stringsReader)) {260return null;261}262return new ImageLocation(attributes, stringsReader);263}264265public ImageLocation findLocation(String name) {266int index = getLocationIndex(name);267if (index < 0) {268return null;269}270long[] attributes = getAttributes(offsets.get(index));271if (!ImageLocation.verify(name, attributes, stringsReader)) {272return null;273}274return new ImageLocation(attributes, stringsReader);275}276277public boolean verifyLocation(String module, String name) {278int index = getLocationIndex(module, name);279if (index < 0) {280return false;281}282int locationOffset = offsets.get(index);283return ImageLocation.verify(module, name, locations, locationOffset, stringsReader);284}285286// Details of the algorithm used here can be found in287// jdk.tools.jlink.internal.PerfectHashBuilder.288public int getLocationIndex(String name) {289int count = header.getTableLength();290int index = redirect.get(ImageStringsReader.hashCode(name) % count);291if (index < 0) {292// index is twos complement of location attributes index.293return -index - 1;294} else if (index > 0) {295// index is hash seed needed to compute location attributes index.296return ImageStringsReader.hashCode(name, index) % count;297} else {298// No entry.299return -1;300}301}302303private int getLocationIndex(String module, String name) {304int count = header.getTableLength();305int index = redirect.get(ImageStringsReader.hashCode(module, name) % count);306if (index < 0) {307// index is twos complement of location attributes index.308return -index - 1;309} else if (index > 0) {310// index is hash seed needed to compute location attributes index.311return ImageStringsReader.hashCode(module, name, index) % count;312} else {313// No entry.314return -1;315}316}317318public String[] getEntryNames() {319int[] attributeOffsets = new int[offsets.capacity()];320offsets.get(attributeOffsets);321return IntStream.of(attributeOffsets)322.filter(o -> o != 0)323.mapToObj(o -> ImageLocation.readFrom(this, o).getFullName())324.sorted()325.toArray(String[]::new);326}327328ImageLocation getLocation(int offset) {329return ImageLocation.readFrom(this, offset);330}331332public long[] getAttributes(int offset) {333if (offset < 0 || offset >= locations.limit()) {334throw new IndexOutOfBoundsException("offset");335}336return ImageLocation.decompress(locations, offset);337}338339public String getString(int offset) {340if (offset < 0 || offset >= strings.limit()) {341throw new IndexOutOfBoundsException("offset");342}343return ImageStringsReader.stringFromByteBuffer(strings, offset);344}345346public int match(int offset, String string, int stringOffset) {347if (offset < 0 || offset >= strings.limit()) {348throw new IndexOutOfBoundsException("offset");349}350return ImageStringsReader.stringFromByteBufferMatches(strings, offset, string, stringOffset);351}352353private byte[] getBufferBytes(ByteBuffer buffer) {354Objects.requireNonNull(buffer);355byte[] bytes = new byte[buffer.limit()];356buffer.get(bytes);357358return bytes;359}360361private ByteBuffer readBuffer(long offset, long size) {362if (offset < 0 || Integer.MAX_VALUE <= offset) {363throw new IndexOutOfBoundsException("Bad offset: " + offset);364}365366if (size < 0 || Integer.MAX_VALUE <= size) {367throw new IndexOutOfBoundsException("Bad size: " + size);368}369370if (MAP_ALL) {371ByteBuffer buffer = slice(memoryMap, (int)offset, (int)size);372buffer.order(ByteOrder.BIG_ENDIAN);373374return buffer;375} else {376if (channel == null) {377throw new InternalError("Image file channel not open");378}379380ByteBuffer buffer = ImageBufferCache.getBuffer(size);381int read;382try {383read = channel.read(buffer, offset);384buffer.rewind();385} catch (IOException ex) {386ImageBufferCache.releaseBuffer(buffer);387throw new RuntimeException(ex);388}389390if (read != size) {391ImageBufferCache.releaseBuffer(buffer);392throw new RuntimeException("Short read: " + read +393" instead of " + size + " bytes");394}395396return buffer;397}398}399400public byte[] getResource(String name) {401Objects.requireNonNull(name);402ImageLocation location = findLocation(name);403404return location != null ? getResource(location) : null;405}406407public byte[] getResource(ImageLocation loc) {408ByteBuffer buffer = getResourceBuffer(loc);409410if (buffer != null) {411byte[] bytes = getBufferBytes(buffer);412ImageBufferCache.releaseBuffer(buffer);413414return bytes;415}416417return null;418}419420public ByteBuffer getResourceBuffer(ImageLocation loc) {421Objects.requireNonNull(loc);422long offset = loc.getContentOffset() + indexSize;423long compressedSize = loc.getCompressedSize();424long uncompressedSize = loc.getUncompressedSize();425426if (compressedSize < 0 || Integer.MAX_VALUE < compressedSize) {427throw new IndexOutOfBoundsException(428"Bad compressed size: " + compressedSize);429}430431if (uncompressedSize < 0 || Integer.MAX_VALUE < uncompressedSize) {432throw new IndexOutOfBoundsException(433"Bad uncompressed size: " + uncompressedSize);434}435436if (compressedSize == 0) {437return readBuffer(offset, uncompressedSize);438} else {439ByteBuffer buffer = readBuffer(offset, compressedSize);440441if (buffer != null) {442byte[] bytesIn = getBufferBytes(buffer);443ImageBufferCache.releaseBuffer(buffer);444byte[] bytesOut;445446try {447bytesOut = decompressor.decompressResource(byteOrder,448(int strOffset) -> getString(strOffset), bytesIn);449} catch (IOException ex) {450throw new RuntimeException(ex);451}452453return ByteBuffer.wrap(bytesOut);454}455}456457return null;458}459460public InputStream getResourceStream(ImageLocation loc) {461Objects.requireNonNull(loc);462byte[] bytes = getResource(loc);463464return new ByteArrayInputStream(bytes);465}466}467468469