1 /* 2 * Copyright (c) 2014, 2017, 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 Path imagePath; 71 private final ByteOrder byteOrder; 72 private final String name; 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 this.imagePath = Objects.requireNonNull(path); 87 this.byteOrder = Objects.requireNonNull(byteOrder); 88 this.name = this.imagePath.toString(); 89 90 ByteBuffer map; 91 92 if (USE_JVM_MAP && BasicImageReader.class.getClassLoader() == null) { 93 // Check to see if the jvm has opened the file using libjimage 94 // native entry when loading the image for this runtime 95 map = NativeImageBuffer.getNativeMap(name); 96 } else { 97 map = null; 98 } 99 100 // Open the file only if no memory map yet or is 32 bit jvm 101 if (map != null && MAP_ALL) { 102 channel = null; 103 } else { 104 channel = FileChannel.open(imagePath, StandardOpenOption.READ); 105 // No lambdas during bootstrap 106 AccessController.doPrivileged(new PrivilegedAction<Void>() { 107 @Override 108 public Void run() { 109 if (BasicImageReader.class.getClassLoader() == null) { 110 try { 111 Class<?> fileChannelImpl = 112 Class.forName("sun.nio.ch.FileChannelImpl"); 113 Method setUninterruptible = 114 fileChannelImpl.getMethod("setUninterruptible"); 115 setUninterruptible.invoke(channel); 116 } catch (ClassNotFoundException | 117 NoSuchMethodException | 118 IllegalAccessException | 119 InvocationTargetException ex) { 120 // fall thru - will only happen on JDK-8 systems where this code 121 // is only used by tools using jrt-fs (non-critical.) 122 } 123 } 124 125 return null; 126 } 127 }); 128 } 129 130 // If no memory map yet and 64 bit jvm then memory map entire file 131 if (MAP_ALL && map == null) { 132 map = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); 133 } 134 135 // Assume we have a memory map to read image file header 136 ByteBuffer headerBuffer = map; 137 int headerSize = ImageHeader.getHeaderSize(); 138 139 // If no memory map then read header from image file 140 if (headerBuffer == null) { 141 headerBuffer = ByteBuffer.allocateDirect(headerSize); 142 if (channel.read(headerBuffer, 0L) == headerSize) { 143 headerBuffer.rewind(); 144 } else { 145 throw new IOException("\"" + name + "\" is not an image file"); 146 } 147 } else if (headerBuffer.capacity() < headerSize) { 148 throw new IOException("\"" + name + "\" is not an image file"); 149 } 150 151 // Interpret the image file header 152 header = readHeader(intBuffer(headerBuffer, 0, headerSize)); 153 indexSize = header.getIndexSize(); 154 155 // If no memory map yet then must be 32 bit jvm not previously mapped 156 if (map == null) { 157 // Just map the image index 158 map = channel.map(FileChannel.MapMode.READ_ONLY, 0, indexSize); 159 } 160 161 memoryMap = map.asReadOnlyBuffer(); 162 163 // Interpret the image index 164 if (memoryMap.capacity() < indexSize) { 165 throw new IOException("The image file \"" + name + "\" is corrupted"); 166 } 167 redirect = intBuffer(memoryMap, header.getRedirectOffset(), header.getRedirectSize()); 168 offsets = intBuffer(memoryMap, header.getOffsetsOffset(), header.getOffsetsSize()); 169 locations = slice(memoryMap, header.getLocationsOffset(), header.getLocationsSize()); 170 strings = slice(memoryMap, header.getStringsOffset(), header.getStringsSize()); 171 172 stringsReader = new ImageStringsReader(this); 173 decompressor = new Decompressor(); 174 } 175 176 protected BasicImageReader(Path imagePath) throws IOException { 177 this(imagePath, ByteOrder.nativeOrder()); 178 } 179 180 public static BasicImageReader open(Path imagePath) throws IOException { 181 return new BasicImageReader(imagePath, ByteOrder.nativeOrder()); 182 } 183 184 public ImageHeader getHeader() { 185 return header; 186 } 187 188 private ImageHeader readHeader(IntBuffer buffer) throws IOException { 189 ImageHeader result = ImageHeader.readFrom(buffer); 190 191 if (result.getMagic() != ImageHeader.MAGIC) { 192 throw new IOException("\"" + name + "\" is not an image file"); 193 } 194 195 if (result.getMajorVersion() != ImageHeader.MAJOR_VERSION || 196 result.getMinorVersion() != ImageHeader.MINOR_VERSION) { 197 throw new IOException("The image file \"" + name + "\" is not " + 198 "the correct version. Major: " + result.getMajorVersion() + 199 ". Minor: " + result.getMinorVersion()); 200 } 201 202 return result; 203 } 204 205 private static ByteBuffer slice(ByteBuffer buffer, int position, int capacity) { 206 // Note that this is the only limit and position manipulation of 207 // BasicImageReader private ByteBuffers. The synchronize could be avoided 208 // by cloning the buffer to make a local copy, but at the cost of creating 209 // a new object. 210 synchronized(buffer) { 211 buffer.limit(position + capacity); 212 buffer.position(position); 213 return buffer.slice(); 214 } 215 } 216 217 private IntBuffer intBuffer(ByteBuffer buffer, int offset, int size) { 218 return slice(buffer, offset, size).order(byteOrder).asIntBuffer(); 219 } 220 221 public static void releaseByteBuffer(ByteBuffer buffer) { 222 Objects.requireNonNull(buffer); 223 224 if (!MAP_ALL) { 225 ImageBufferCache.releaseBuffer(buffer); 226 } 227 } 228 229 public String getName() { 230 return name; 231 } 232 233 public ByteOrder getByteOrder() { 234 return byteOrder; 235 } 236 237 public Path getImagePath() { 238 return imagePath; 239 } 240 241 @Override 242 public void close() throws IOException { 243 if (channel != null) { 244 channel.close(); 245 } 246 } 247 248 public ImageStringsReader getStrings() { 249 return stringsReader; 250 } 251 252 public ImageLocation findLocation(String mn, String rn) { 253 Objects.requireNonNull(mn); 254 Objects.requireNonNull(rn); 255 256 return findLocation("/" + mn + "/" + rn); 257 } 258 259 public synchronized ImageLocation findLocation(String name) { 260 Objects.requireNonNull(name); 261 // Details of the algorithm used here can be found in 262 // jdk.tools.jlink.internal.PerfectHashBuilder. 263 byte[] bytes = ImageStringsReader.mutf8FromString(name); 264 int count = header.getTableLength(); 265 int index = redirect.get(ImageStringsReader.hashCode(bytes) % count); 266 267 if (index < 0) { 268 // index is twos complement of location attributes index. 269 index = -index - 1; 270 } else if (index > 0) { 271 // index is hash seed needed to compute location attributes index. 272 index = ImageStringsReader.hashCode(bytes, index) % count; 273 } else { 274 // No entry. 275 return null; 276 } 277 278 long[] attributes = getAttributes(offsets.get(index)); 279 280 ImageLocation imageLocation = new ImageLocation(attributes, stringsReader); 281 282 if (!imageLocation.verify(name)) { 283 return null; 284 } 285 286 return imageLocation; 287 } 288 289 public String[] getEntryNames() { 290 int[] attributeOffsets = new int[offsets.capacity()]; 291 offsets.get(attributeOffsets); 292 return IntStream.of(attributeOffsets) 293 .filter(o -> o != 0) 294 .mapToObj(o -> ImageLocation.readFrom(this, o).getFullName()) 295 .sorted() 296 .toArray(String[]::new); 297 } 298 299 ImageLocation getLocation(int offset) { 300 return ImageLocation.readFrom(this, offset); 301 } 302 303 public long[] getAttributes(int offset) { 304 if (offset < 0 || offset >= locations.limit()) { 305 throw new IndexOutOfBoundsException("offset"); 306 } 307 308 ByteBuffer buffer = slice(locations, offset, locations.limit() - offset); 309 return ImageLocation.decompress(buffer); 310 } 311 312 public String getString(int offset) { 313 if (offset < 0 || offset >= strings.limit()) { 314 throw new IndexOutOfBoundsException("offset"); 315 } 316 317 ByteBuffer buffer = slice(strings, offset, strings.limit() - offset); 318 return ImageStringsReader.stringFromByteBuffer(buffer); 319 } 320 321 private byte[] getBufferBytes(ByteBuffer buffer) { 322 Objects.requireNonNull(buffer); 323 byte[] bytes = new byte[buffer.limit()]; 324 buffer.get(bytes); 325 326 return bytes; 327 } 328 329 private ByteBuffer readBuffer(long offset, long size) { 330 if (offset < 0 || Integer.MAX_VALUE <= offset) { 331 throw new IndexOutOfBoundsException("Bad offset: " + offset); 332 } 333 334 if (size < 0 || Integer.MAX_VALUE <= size) { 335 throw new IndexOutOfBoundsException("Bad size: " + size); 336 } 337 338 if (MAP_ALL) { 339 ByteBuffer buffer = slice(memoryMap, (int)offset, (int)size); 340 buffer.order(ByteOrder.BIG_ENDIAN); 341 342 return buffer; 343 } else { 344 if (channel == null) { 345 throw new InternalError("Image file channel not open"); 346 } 347 348 ByteBuffer buffer = ImageBufferCache.getBuffer(size); 349 int read; 350 try { 351 read = channel.read(buffer, offset); 352 buffer.rewind(); 353 } catch (IOException ex) { 354 ImageBufferCache.releaseBuffer(buffer); 355 throw new RuntimeException(ex); 356 } 357 358 if (read != size) { 359 ImageBufferCache.releaseBuffer(buffer); 360 throw new RuntimeException("Short read: " + read + 361 " instead of " + size + " bytes"); 362 } 363 364 return buffer; 365 } 366 } 367 368 public byte[] getResource(String name) { 369 Objects.requireNonNull(name); 370 ImageLocation location = findLocation(name); 371 372 return location != null ? getResource(location) : null; 373 } 374 375 public byte[] getResource(ImageLocation loc) { 376 ByteBuffer buffer = getResourceBuffer(loc); 377 378 if (buffer != null) { 379 byte[] bytes = getBufferBytes(buffer); 380 ImageBufferCache.releaseBuffer(buffer); 381 382 return bytes; 383 } 384 385 return null; 386 } 387 388 public ByteBuffer getResourceBuffer(ImageLocation loc) { 389 Objects.requireNonNull(loc); 390 long offset = loc.getContentOffset() + indexSize; 391 long compressedSize = loc.getCompressedSize(); 392 long uncompressedSize = loc.getUncompressedSize(); 393 394 if (compressedSize < 0 || Integer.MAX_VALUE < compressedSize) { 395 throw new IndexOutOfBoundsException( 396 "Bad compressed size: " + compressedSize); 397 } 398 399 if (uncompressedSize < 0 || Integer.MAX_VALUE < uncompressedSize) { 400 throw new IndexOutOfBoundsException( 401 "Bad uncompressed size: " + uncompressedSize); 402 } 403 404 if (compressedSize == 0) { 405 return readBuffer(offset, uncompressedSize); 406 } else { 407 ByteBuffer buffer = readBuffer(offset, compressedSize); 408 409 if (buffer != null) { 410 byte[] bytesIn = getBufferBytes(buffer); 411 ImageBufferCache.releaseBuffer(buffer); 412 byte[] bytesOut; 413 414 try { 415 bytesOut = decompressor.decompressResource(byteOrder, 416 (int strOffset) -> getString(strOffset), bytesIn); 417 } catch (IOException ex) { 418 throw new RuntimeException(ex); 419 } 420 421 return ByteBuffer.wrap(bytesOut); 422 } 423 } 424 425 return null; 426 } 427 428 public InputStream getResourceStream(ImageLocation loc) { 429 Objects.requireNonNull(loc); 430 byte[] bytes = getResource(loc); 431 432 return new ByteArrayInputStream(bytes); 433 } 434 }