1 /* 2 * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.internal.jimage; 26 27 import java.io.ByteArrayInputStream; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.lang.reflect.InvocationTargetException; 31 import java.lang.reflect.Method; 32 import java.nio.ByteBuffer; 33 import java.nio.ByteOrder; 34 import java.nio.IntBuffer; 35 import java.nio.channels.FileChannel; 36 import java.nio.file.Path; 37 import java.nio.file.StandardOpenOption; 38 import java.security.AccessController; 39 import java.security.PrivilegedAction; 40 import java.util.Objects; 41 import java.util.stream.IntStream; 42 import jdk.internal.jimage.decompressor.Decompressor; 43 44 /** 45 * @implNote This class needs to maintain JDK 8 source compatibility. 46 * 47 * It is used internally in the JDK to implement jimage/jrtfs access, 48 * but also compiled and delivered as part of the jrtfs.jar to support access 49 * to the jimage file provided by the shipped JDK by tools running on JDK 8. 50 */ 51 public class BasicImageReader implements AutoCloseable { 52 private static boolean isSystemProperty(String key, String value, String def) { 53 // No lambdas during bootstrap 54 return AccessController.doPrivileged( 55 new PrivilegedAction<Boolean>() { 56 @Override 57 public Boolean run() { 58 return value.equals(System.getProperty(key, def)); 59 } 60 }); 61 } 62 63 static private final boolean IS_64_BIT = 64 isSystemProperty("sun.arch.data.model", "64", "32"); 65 static private final boolean USE_JVM_MAP = 66 isSystemProperty("jdk.image.use.jvm.map", "true", "true"); 67 static private final boolean MAP_ALL = 68 isSystemProperty("jdk.image.map.all", "true", IS_64_BIT ? "true" : "false"); 69 70 private final String name; 71 private final ByteOrder byteOrder; 72 private final Path imagePath; 73 private final ByteBuffer memoryMap; 74 private final FileChannel channel; 75 private final ImageHeader header; 76 private final long indexSize; 77 private final IntBuffer redirect; 78 private final IntBuffer offsets; 79 private final ByteBuffer locations; 80 private final ByteBuffer strings; 81 private final ImageStringsReader stringsReader; 82 private final Decompressor decompressor; 83 84 protected BasicImageReader(Path path, ByteOrder byteOrder) 85 throws IOException { 86 Objects.requireNonNull(path); 87 Objects.requireNonNull(byteOrder); 88 this.name = path.toString(); 89 this.byteOrder = byteOrder; 90 imagePath = path; 91 92 ByteBuffer map; 93 94 if (USE_JVM_MAP && BasicImageReader.class.getClassLoader() == null) { 95 // Check to see if the jvm has opened the file using libjimage 96 // native entry when loading the image for this runtime 97 map = NativeImageBuffer.getNativeMap(name); 98 } else { 99 map = null; 100 } 101 102 // Open the file only if no memory map yet or is 32 bit jvm 103 if (map != null && MAP_ALL) { 104 channel = null; 105 } else { 106 channel = FileChannel.open(imagePath, StandardOpenOption.READ); 107 // No lambdas during bootstrap 108 AccessController.doPrivileged(new PrivilegedAction<Void>() { 109 @Override 110 public Void run() { 111 if (BasicImageReader.class.getClassLoader() == null) { 112 try { 113 Class<?> fileChannelImpl = 114 Class.forName("sun.nio.ch.FileChannelImpl"); 115 Method setUninterruptible = 116 fileChannelImpl.getMethod("setUninterruptible"); 117 setUninterruptible.invoke(channel); 118 } catch (ClassNotFoundException | 119 NoSuchMethodException | 120 IllegalAccessException | 121 InvocationTargetException ex) { 122 // fall thru - will only happen on JDK-8 systems where this code 123 // is only used by tools using jrt-fs (non-critical.) 124 } 125 } 126 127 return null; 128 } 129 }); 130 } 131 132 // If no memory map yet and 64 bit jvm then memory map entire file 133 if (MAP_ALL && map == null) { 134 map = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); 135 } 136 137 // Assume we have a memory map to read image file header 138 ByteBuffer headerBuffer = map; 139 int headerSize = ImageHeader.getHeaderSize(); 140 141 // If no memory map then read header from image file 142 if (map == null) { 143 headerBuffer = ByteBuffer.allocateDirect(headerSize); 144 channel.read(headerBuffer, 0L); 145 headerBuffer.rewind(); 146 } 147 148 // Interpret the image file header 149 header = readHeader(intBuffer(headerBuffer, 0, headerSize)); 150 indexSize = header.getIndexSize(); 151 152 // If no memory map yet then must be 32 bit jvm not previously mapped 153 if (map == null) { 154 // Just map the image index 155 map = channel.map(FileChannel.MapMode.READ_ONLY, 0, indexSize); 156 } 157 158 memoryMap = map.asReadOnlyBuffer(); 159 160 // Interpret the image index 161 redirect = intBuffer(memoryMap, header.getRedirectOffset(), header.getRedirectSize()); 162 offsets = intBuffer(memoryMap, header.getOffsetsOffset(), header.getOffsetsSize()); 163 locations = slice(memoryMap, header.getLocationsOffset(), header.getLocationsSize()); 164 strings = slice(memoryMap, header.getStringsOffset(), header.getStringsSize()); 165 166 stringsReader = new ImageStringsReader(this); 167 decompressor = new Decompressor(); 168 } 169 170 protected BasicImageReader(Path imagePath) throws IOException { 171 this(imagePath, ByteOrder.nativeOrder()); 172 } 173 174 public static BasicImageReader open(Path imagePath) throws IOException { 175 return new BasicImageReader(imagePath, ByteOrder.nativeOrder()); 176 } 177 178 public ImageHeader getHeader() { 179 return header; 180 } 181 182 private ImageHeader readHeader(IntBuffer buffer) throws IOException { 183 ImageHeader result = ImageHeader.readFrom(buffer); 184 185 if (result.getMagic() != ImageHeader.MAGIC) { 186 throw new IOException("\"" + name + "\" is not an image file"); 187 } 188 189 if (result.getMajorVersion() != ImageHeader.MAJOR_VERSION || 190 result.getMinorVersion() != ImageHeader.MINOR_VERSION) { 191 throw new IOException("The image file \"" + name + "\" is not the correct version"); 192 } 193 194 return result; 195 } 196 197 private static ByteBuffer slice(ByteBuffer buffer, int position, int capacity) { 198 // Note that this is the only limit and position manipulation of 199 // BasicImageReader private ByteBuffers. The synchronize could be avoided 200 // by cloning the buffer to make a local copy, but at the cost of creating 201 // a new object. 202 synchronized(buffer) { 203 buffer.limit(position + capacity); 204 buffer.position(position); 205 return buffer.slice(); 206 } 207 } 208 209 private IntBuffer intBuffer(ByteBuffer buffer, int offset, int size) { 210 return slice(buffer, offset, size).order(byteOrder).asIntBuffer(); 211 } 212 213 public static void releaseByteBuffer(ByteBuffer buffer) { 214 Objects.requireNonNull(buffer); 215 216 if (!MAP_ALL) { 217 ImageBufferCache.releaseBuffer(buffer); 218 } 219 } 220 221 public String getName() { 222 return name; 223 } 224 225 public ByteOrder getByteOrder() { 226 return byteOrder; 227 } 228 229 public Path getImagePath() { 230 return imagePath; 231 } 232 233 @Override 234 public void close() throws IOException { 235 if (channel != null) { 236 channel.close(); 237 } 238 } 239 240 public ImageStringsReader getStrings() { 241 return stringsReader; 242 } 243 244 public ImageLocation findLocation(String mn, String rn) { 245 Objects.requireNonNull(mn); 246 Objects.requireNonNull(rn); 247 248 return findLocation("/" + mn + "/" + rn); 249 } 250 251 public synchronized ImageLocation findLocation(String name) { 252 Objects.requireNonNull(name); 253 // Details of the algorithm used here can be found in 254 // jdk.tools.jlink.internal.PerfectHashBuilder. 255 byte[] bytes = ImageStringsReader.mutf8FromString(name); 256 int count = header.getTableLength(); 257 int index = redirect.get(ImageStringsReader.hashCode(bytes) % count); 258 259 if (index < 0) { 260 // index is twos complement of location attributes index. 261 index = -index - 1; 262 } else if (index > 0) { 263 // index is hash seed needed to compute location attributes index. 264 index = ImageStringsReader.hashCode(bytes, index) % count; 265 } else { 266 // No entry. 267 return null; 268 } 269 270 long[] attributes = getAttributes(offsets.get(index)); 271 272 ImageLocation imageLocation = new ImageLocation(attributes, stringsReader); 273 274 if (!imageLocation.verify(name)) { 275 return null; 276 } 277 278 return imageLocation; 279 } 280 281 public String[] getEntryNames() { 282 int[] attributeOffsets = new int[offsets.capacity()]; 283 offsets.get(attributeOffsets); 284 return IntStream.of(attributeOffsets) 285 .filter(o -> o != 0) 286 .mapToObj(o -> ImageLocation.readFrom(this, o).getFullName()) 287 .sorted() 288 .toArray(String[]::new); 289 } 290 291 ImageLocation getLocation(int offset) { 292 return ImageLocation.readFrom(this, offset); 293 } 294 295 public long[] getAttributes(int offset) { 296 if (offset < 0 || offset >= locations.limit()) { 297 throw new IndexOutOfBoundsException("offset"); 298 } 299 300 ByteBuffer buffer = slice(locations, offset, locations.limit() - offset); 301 return ImageLocation.decompress(buffer); 302 } 303 304 public String getString(int offset) { 305 if (offset < 0 || offset >= strings.limit()) { 306 throw new IndexOutOfBoundsException("offset"); 307 } 308 309 ByteBuffer buffer = slice(strings, offset, strings.limit() - offset); 310 return ImageStringsReader.stringFromByteBuffer(buffer); 311 } 312 313 private byte[] getBufferBytes(ByteBuffer buffer) { 314 Objects.requireNonNull(buffer); 315 byte[] bytes = new byte[buffer.limit()]; 316 buffer.get(bytes); 317 318 return bytes; 319 } 320 321 private ByteBuffer readBuffer(long offset, long size) { 322 if (offset < 0 || Integer.MAX_VALUE <= offset) { 323 throw new IndexOutOfBoundsException("offset"); 324 } 325 326 if (size < 0 || Integer.MAX_VALUE <= size) { 327 throw new IndexOutOfBoundsException("size"); 328 } 329 330 if (MAP_ALL) { 331 ByteBuffer buffer = slice(memoryMap, (int)offset, (int)size); 332 buffer.order(ByteOrder.BIG_ENDIAN); 333 334 return buffer; 335 } else { 336 if (channel == null) { 337 throw new InternalError("Image file channel not open"); 338 } 339 340 ByteBuffer buffer = ImageBufferCache.getBuffer(size); 341 int read; 342 try { 343 read = channel.read(buffer, offset); 344 buffer.rewind(); 345 } catch (IOException ex) { 346 ImageBufferCache.releaseBuffer(buffer); 347 throw new RuntimeException(ex); 348 } 349 350 if (read != size) { 351 ImageBufferCache.releaseBuffer(buffer); 352 throw new RuntimeException("Short read: " + read + 353 " instead of " + size + " bytes"); 354 } 355 356 return buffer; 357 } 358 } 359 360 public byte[] getResource(String name) { 361 Objects.requireNonNull(name); 362 ImageLocation location = findLocation(name); 363 364 return location != null ? getResource(location) : null; 365 } 366 367 public byte[] getResource(ImageLocation loc) { 368 ByteBuffer buffer = getResourceBuffer(loc); 369 370 if (buffer != null) { 371 byte[] bytes = getBufferBytes(buffer); 372 ImageBufferCache.releaseBuffer(buffer); 373 374 return bytes; 375 } 376 377 return null; 378 } 379 380 public ByteBuffer getResourceBuffer(ImageLocation loc) { 381 Objects.requireNonNull(loc); 382 long offset = loc.getContentOffset() + indexSize; 383 long compressedSize = loc.getCompressedSize(); 384 long uncompressedSize = loc.getUncompressedSize(); 385 386 if (compressedSize < 0 || Integer.MAX_VALUE < compressedSize) { 387 throw new IndexOutOfBoundsException("Compressed size"); 388 } 389 390 if (uncompressedSize < 0 || Integer.MAX_VALUE < uncompressedSize) { 391 throw new IndexOutOfBoundsException("Uncompressed size"); 392 } 393 394 if (compressedSize == 0) { 395 return readBuffer(offset, uncompressedSize); 396 } else { 397 ByteBuffer buffer = readBuffer(offset, compressedSize); 398 399 if (buffer != null) { 400 byte[] bytesIn = getBufferBytes(buffer); 401 ImageBufferCache.releaseBuffer(buffer); 402 byte[] bytesOut; 403 404 try { 405 bytesOut = decompressor.decompressResource(byteOrder, 406 (int strOffset) -> getString(strOffset), bytesIn); 407 } catch (IOException ex) { 408 throw new RuntimeException(ex); 409 } 410 411 return ByteBuffer.wrap(bytesOut); 412 } 413 } 414 415 return null; 416 } 417 418 public InputStream getResourceStream(ImageLocation loc) { 419 Objects.requireNonNull(loc); 420 byte[] bytes = getResource(loc); 421 422 return new ByteArrayInputStream(bytes); 423 } 424 }