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 int count = header.getTableLength(); 264 int index = redirect.get(ImageStringsReader.hashCode(name) % count); 265 266 if (index < 0) { 267 // index is twos complement of location attributes index. 268 index = -index - 1; 269 } else if (index > 0) { 270 // index is hash seed needed to compute location attributes index. 271 index = ImageStringsReader.hashCode(name, index) % count; 272 } else { 273 // No entry. 274 return null; 275 } 276 277 long[] attributes = getAttributes(offsets.get(index)); 278 279 if (!ImageLocation.verify(name, attributes, stringsReader)) { 280 return null; 281 } 282 return new ImageLocation(attributes, stringsReader); 283 } 284 285 public String[] getEntryNames() { 286 int[] attributeOffsets = new int[offsets.capacity()]; 287 offsets.get(attributeOffsets); 288 return IntStream.of(attributeOffsets) 289 .filter(o -> o != 0) 290 .mapToObj(o -> ImageLocation.readFrom(this, o).getFullName()) 291 .sorted() 292 .toArray(String[]::new); 293 } 294 295 ImageLocation getLocation(int offset) { 296 return ImageLocation.readFrom(this, offset); 297 } 298 299 public long[] getAttributes(int offset) { 300 if (offset < 0 || offset >= locations.limit()) { 301 throw new IndexOutOfBoundsException("offset"); 302 } 303 304 ByteBuffer buffer = slice(locations, offset, locations.limit() - offset); 305 return ImageLocation.decompress(buffer); 306 } 307 308 public String getString(int offset) { 309 if (offset < 0 || offset >= strings.limit()) { 310 throw new IndexOutOfBoundsException("offset"); 311 } 312 313 ByteBuffer buffer = slice(strings, offset, strings.limit() - offset); 314 return ImageStringsReader.stringFromByteBuffer(buffer); 315 } 316 317 private byte[] getBufferBytes(ByteBuffer buffer) { 318 Objects.requireNonNull(buffer); 319 byte[] bytes = new byte[buffer.limit()]; 320 buffer.get(bytes); 321 322 return bytes; 323 } 324 325 private ByteBuffer readBuffer(long offset, long size) { 326 if (offset < 0 || Integer.MAX_VALUE <= offset) { 327 throw new IndexOutOfBoundsException("Bad offset: " + offset); 328 } 329 330 if (size < 0 || Integer.MAX_VALUE <= size) { 331 throw new IndexOutOfBoundsException("Bad size: " + size); 332 } 333 334 if (MAP_ALL) { 335 ByteBuffer buffer = slice(memoryMap, (int)offset, (int)size); 336 buffer.order(ByteOrder.BIG_ENDIAN); 337 338 return buffer; 339 } else { 340 if (channel == null) { 341 throw new InternalError("Image file channel not open"); 342 } 343 344 ByteBuffer buffer = ImageBufferCache.getBuffer(size); 345 int read; 346 try { 347 read = channel.read(buffer, offset); 348 buffer.rewind(); 349 } catch (IOException ex) { 350 ImageBufferCache.releaseBuffer(buffer); 351 throw new RuntimeException(ex); 352 } 353 354 if (read != size) { 355 ImageBufferCache.releaseBuffer(buffer); 356 throw new RuntimeException("Short read: " + read + 357 " instead of " + size + " bytes"); 358 } 359 360 return buffer; 361 } 362 } 363 364 public byte[] getResource(String name) { 365 Objects.requireNonNull(name); 366 ImageLocation location = findLocation(name); 367 368 return location != null ? getResource(location) : null; 369 } 370 371 public byte[] getResource(ImageLocation loc) { 372 ByteBuffer buffer = getResourceBuffer(loc); 373 374 if (buffer != null) { 375 byte[] bytes = getBufferBytes(buffer); 376 ImageBufferCache.releaseBuffer(buffer); 377 378 return bytes; 379 } 380 381 return null; 382 } 383 384 public ByteBuffer getResourceBuffer(ImageLocation loc) { 385 Objects.requireNonNull(loc); 386 long offset = loc.getContentOffset() + indexSize; 387 long compressedSize = loc.getCompressedSize(); 388 long uncompressedSize = loc.getUncompressedSize(); 389 390 if (compressedSize < 0 || Integer.MAX_VALUE < compressedSize) { 391 throw new IndexOutOfBoundsException( 392 "Bad compressed size: " + compressedSize); 393 } 394 395 if (uncompressedSize < 0 || Integer.MAX_VALUE < uncompressedSize) { 396 throw new IndexOutOfBoundsException( 397 "Bad uncompressed size: " + uncompressedSize); 398 } 399 400 if (compressedSize == 0) { 401 return readBuffer(offset, uncompressedSize); 402 } else { 403 ByteBuffer buffer = readBuffer(offset, compressedSize); 404 405 if (buffer != null) { 406 byte[] bytesIn = getBufferBytes(buffer); 407 ImageBufferCache.releaseBuffer(buffer); 408 byte[] bytesOut; 409 410 try { 411 bytesOut = decompressor.decompressResource(byteOrder, 412 (int strOffset) -> getString(strOffset), bytesIn); 413 } catch (IOException ex) { 414 throw new RuntimeException(ex); 415 } 416 417 return ByteBuffer.wrap(bytesOut); 418 } 419 } 420 421 return null; 422 } 423 424 public InputStream getResourceStream(ImageLocation loc) { 425 Objects.requireNonNull(loc); 426 byte[] bytes = getResource(loc); 427 428 return new ByteArrayInputStream(bytes); 429 } 430 }