1 /* 2 * Copyright (c) 2014, 2020, 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.IOException; 28 import java.io.InputStream; 29 import java.io.UncheckedIOException; 30 import java.nio.ByteBuffer; 31 import java.nio.ByteOrder; 32 import java.nio.IntBuffer; 33 import java.nio.file.Files; 34 import java.nio.file.attribute.BasicFileAttributes; 35 import java.nio.file.attribute.FileTime; 36 import java.nio.file.Path; 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.HashMap; 40 import java.util.HashSet; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.Objects; 44 import java.util.Set; 45 import java.util.function.Consumer; 46 47 /** 48 * @implNote This class needs to maintain JDK 8 source compatibility. 49 * 50 * It is used internally in the JDK to implement jimage/jrtfs access, 51 * but also compiled and delivered as part of the jrtfs.jar to support access 52 * to the jimage file provided by the shipped JDK by tools running on JDK 8. 53 */ 54 public final class ImageReader implements AutoCloseable { 55 private final SharedImageReader reader; 56 57 private volatile boolean closed; 58 59 private ImageReader(SharedImageReader reader) { 60 this.reader = reader; 61 } 62 63 public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException { 64 Objects.requireNonNull(imagePath); 65 Objects.requireNonNull(byteOrder); 66 67 return SharedImageReader.open(imagePath, byteOrder); 68 } 69 70 public static ImageReader open(Path imagePath) throws IOException { 71 return open(imagePath, ByteOrder.nativeOrder()); 72 } 73 74 @Override 75 public void close() throws IOException { 76 if (closed) { 77 throw new IOException("image file already closed"); 78 } 79 reader.close(this); 80 closed = true; 81 } 82 83 private void ensureOpen() throws IOException { 84 if (closed) { 85 throw new IOException("image file closed"); 86 } 87 } 88 89 private void requireOpen() { 90 if (closed) { 91 throw new IllegalStateException("image file closed"); 92 } 93 } 94 95 // directory management interface 96 public Directory getRootDirectory() throws IOException { 97 ensureOpen(); 98 return reader.getRootDirectory(); 99 } 100 101 102 public Node findNode(String name) throws IOException { 103 ensureOpen(); 104 return reader.findNode(name); 105 } 106 107 public byte[] getResource(Node node) throws IOException { 108 ensureOpen(); 109 return reader.getResource(node); 110 } 111 112 public byte[] getResource(Resource rs) throws IOException { 113 ensureOpen(); 114 return reader.getResource(rs); 115 } 116 117 public ImageHeader getHeader() { 118 requireOpen(); 119 return reader.getHeader(); 120 } 121 122 public static void releaseByteBuffer(ByteBuffer buffer) { 123 BasicImageReader.releaseByteBuffer(buffer); 124 } 125 126 public String getName() { 127 requireOpen(); 128 return reader.getName(); 129 } 130 131 public ByteOrder getByteOrder() { 132 requireOpen(); 133 return reader.getByteOrder(); 134 } 135 136 public Path getImagePath() { 137 requireOpen(); 138 return reader.getImagePath(); 139 } 140 141 public ImageStringsReader getStrings() { 142 requireOpen(); 143 return reader.getStrings(); 144 } 145 146 public ImageLocation findLocation(String mn, String rn) { 147 requireOpen(); 148 return reader.findLocation(mn, rn); 149 } 150 151 public ImageLocation findLocation(String name) { 152 requireOpen(); 153 return reader.findLocation(name); 154 } 155 156 public String[] getEntryNames() { 157 requireOpen(); 158 return reader.getEntryNames(); 159 } 160 161 public String[] getModuleNames() { 162 requireOpen(); 163 int off = "/modules/".length(); 164 return reader.findNode("/modules") 165 .getChildren() 166 .stream() 167 .map(Node::getNameString) 168 .map(s -> s.substring(off, s.length())) 169 .toArray(String[]::new); 170 } 171 172 public long[] getAttributes(int offset) { 173 requireOpen(); 174 return reader.getAttributes(offset); 175 } 176 177 public String getString(int offset) { 178 requireOpen(); 179 return reader.getString(offset); 180 } 181 182 public byte[] getResource(String name) { 183 requireOpen(); 184 return reader.getResource(name); 185 } 186 187 public byte[] getResource(ImageLocation loc) { 188 requireOpen(); 189 return reader.getResource(loc); 190 } 191 192 public ByteBuffer getResourceBuffer(ImageLocation loc) { 193 requireOpen(); 194 return reader.getResourceBuffer(loc); 195 } 196 197 public InputStream getResourceStream(ImageLocation loc) { 198 requireOpen(); 199 return reader.getResourceStream(loc); 200 } 201 202 private final static class SharedImageReader extends BasicImageReader { 203 static final int SIZE_OF_OFFSET = Integer.BYTES; 204 205 static final Map<Path, SharedImageReader> OPEN_FILES = new HashMap<>(); 206 207 // List of openers for this shared image. 208 final Set<ImageReader> openers; 209 210 // attributes of the .jimage file. jimage file does not contain 211 // attributes for the individual resources (yet). We use attributes 212 // of the jimage file itself (creation, modification, access times). 213 // Iniitalized lazily, see {@link #imageFileAttributes()}. 214 BasicFileAttributes imageFileAttributes; 215 216 // directory management implementation 217 final HashMap<String, Node> nodes; 218 volatile Directory rootDir; 219 220 Directory packagesDir; 221 Directory modulesDir; 222 223 private SharedImageReader(Path imagePath, ByteOrder byteOrder) throws IOException { 224 super(imagePath, byteOrder); 225 this.openers = new HashSet<>(); 226 this.nodes = new HashMap<>(); 227 } 228 229 public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException { 230 Objects.requireNonNull(imagePath); 231 Objects.requireNonNull(byteOrder); 232 233 synchronized (OPEN_FILES) { 234 SharedImageReader reader = OPEN_FILES.get(imagePath); 235 236 if (reader == null) { 237 // Will fail with an IOException if wrong byteOrder. 238 reader = new SharedImageReader(imagePath, byteOrder); 239 OPEN_FILES.put(imagePath, reader); 240 } else if (reader.getByteOrder() != byteOrder) { 241 throw new IOException("\"" + reader.getName() + "\" is not an image file"); 242 } 243 244 ImageReader image = new ImageReader(reader); 245 reader.openers.add(image); 246 247 return image; 248 } 249 } 250 251 public void close(ImageReader image) throws IOException { 252 Objects.requireNonNull(image); 253 254 synchronized (OPEN_FILES) { 255 if (!openers.remove(image)) { 256 throw new IOException("image file already closed"); 257 } 258 259 if (openers.isEmpty()) { 260 close(); 261 nodes.clear(); 262 rootDir = null; 263 264 if (!OPEN_FILES.remove(this.getImagePath(), this)) { 265 throw new IOException("image file not found in open list"); 266 } 267 } 268 } 269 } 270 271 void addOpener(ImageReader reader) { 272 synchronized (OPEN_FILES) { 273 openers.add(reader); 274 } 275 } 276 277 boolean removeOpener(ImageReader reader) { 278 synchronized (OPEN_FILES) { 279 return openers.remove(reader); 280 } 281 } 282 283 // directory management interface 284 Directory getRootDirectory() { 285 return buildRootDirectory(); 286 } 287 288 /** 289 * Lazily build a node from a name. 290 */ 291 synchronized Node buildNode(String name) { 292 Node n; 293 boolean isPackages = name.startsWith("/packages"); 294 boolean isModules = !isPackages && name.startsWith("/modules"); 295 296 if (!(isModules || isPackages)) { 297 return null; 298 } 299 300 ImageLocation loc = findLocation(name); 301 302 if (loc != null) { // A sub tree node 303 if (isPackages) { 304 n = handlePackages(name, loc); 305 } else { // modules sub tree 306 n = handleModulesSubTree(name, loc); 307 } 308 } else { // Asking for a resource? /modules/java.base/java/lang/Object.class 309 if (isModules) { 310 n = handleResource(name); 311 } else { 312 // Possibly ask for /packages/java.lang/java.base 313 // although /packages/java.base not created 314 n = handleModuleLink(name); 315 } 316 } 317 return n; 318 } 319 320 synchronized Directory buildRootDirectory() { 321 Directory root = rootDir; // volatile read 322 if (root != null) { 323 return root; 324 } 325 326 root = newDirectory(null, "/"); 327 root.setIsRootDir(); 328 329 // /packages dir 330 packagesDir = newDirectory(root, "/packages"); 331 packagesDir.setIsPackagesDir(); 332 333 // /modules dir 334 modulesDir = newDirectory(root, "/modules"); 335 modulesDir.setIsModulesDir(); 336 337 root.setCompleted(true); 338 return rootDir = root; 339 } 340 341 /** 342 * To visit sub tree resources. 343 */ 344 interface LocationVisitor { 345 void visit(ImageLocation loc); 346 } 347 348 void visitLocation(ImageLocation loc, LocationVisitor visitor) { 349 byte[] offsets = getResource(loc); 350 ByteBuffer buffer = ByteBuffer.wrap(offsets); 351 buffer.order(getByteOrder()); 352 IntBuffer intBuffer = buffer.asIntBuffer(); 353 for (int i = 0; i < offsets.length / SIZE_OF_OFFSET; i++) { 354 int offset = intBuffer.get(i); 355 ImageLocation pkgLoc = getLocation(offset); 356 visitor.visit(pkgLoc); 357 } 358 } 359 360 void visitPackageLocation(ImageLocation loc) { 361 // Retrieve package name 362 String pkgName = getBaseExt(loc); 363 // Content is array of offsets in Strings table 364 byte[] stringsOffsets = getResource(loc); 365 ByteBuffer buffer = ByteBuffer.wrap(stringsOffsets); 366 buffer.order(getByteOrder()); 367 IntBuffer intBuffer = buffer.asIntBuffer(); 368 // For each module, create a link node. 369 for (int i = 0; i < stringsOffsets.length / SIZE_OF_OFFSET; i++) { 370 // skip empty state, useless. 371 intBuffer.get(i); 372 i++; 373 int offset = intBuffer.get(i); 374 String moduleName = getString(offset); 375 Node targetNode = findNode("/modules/" + moduleName); 376 if (targetNode != null) { 377 String pkgDirName = packagesDir.getName() + "/" + pkgName; 378 Directory pkgDir = (Directory) nodes.get(pkgDirName); 379 newLinkNode(pkgDir, pkgDir.getName() + "/" + moduleName, targetNode); 380 } 381 } 382 } 383 384 Node handlePackages(String name, ImageLocation loc) { 385 long size = loc.getUncompressedSize(); 386 Node n = null; 387 // Only possiblities are /packages, /packages/package/module 388 if (name.equals("/packages")) { 389 visitLocation(loc, (childloc) -> { 390 findNode(childloc.getFullName()); 391 }); 392 packagesDir.setCompleted(true); 393 n = packagesDir; 394 } else { 395 if (size != 0) { // children are offsets to module in StringsTable 396 String pkgName = getBaseExt(loc); 397 Directory pkgDir = newDirectory(packagesDir, packagesDir.getName() + "/" + pkgName); 398 visitPackageLocation(loc); 399 pkgDir.setCompleted(true); 400 n = pkgDir; 401 } else { // Link to module 402 String pkgName = loc.getParent(); 403 String modName = getBaseExt(loc); 404 Node targetNode = findNode("/modules/" + modName); 405 if (targetNode != null) { 406 String pkgDirName = packagesDir.getName() + "/" + pkgName; 407 Directory pkgDir = (Directory) nodes.get(pkgDirName); 408 Node linkNode = newLinkNode(pkgDir, pkgDir.getName() + "/" + modName, targetNode); 409 n = linkNode; 410 } 411 } 412 } 413 return n; 414 } 415 416 // Asking for /packages/package/module although 417 // /packages/<pkg>/ not yet created, need to create it 418 // prior to return the link to module node. 419 Node handleModuleLink(String name) { 420 // eg: unresolved /packages/package/module 421 // Build /packages/package node 422 Node ret = null; 423 String radical = "/packages/"; 424 String path = name; 425 if (path.startsWith(radical)) { 426 int start = radical.length(); 427 int pkgEnd = path.indexOf('/', start); 428 if (pkgEnd != -1) { 429 String pkg = path.substring(start, pkgEnd); 430 String pkgPath = radical + pkg; 431 Node n = findNode(pkgPath); 432 // If not found means that this is a symbolic link such as: 433 // /packages/java.util/java.base/java/util/Vector.class 434 // and will be done by a retry of the filesystem 435 for (Node child : n.getChildren()) { 436 if (child.name.equals(name)) { 437 ret = child; 438 break; 439 } 440 } 441 } 442 } 443 return ret; 444 } 445 446 Node handleModulesSubTree(String name, ImageLocation loc) { 447 Node n; 448 assert (name.equals(loc.getFullName())); 449 Directory dir = makeDirectories(name); 450 visitLocation(loc, (childloc) -> { 451 String path = childloc.getFullName(); 452 if (path.startsWith("/modules")) { // a package 453 makeDirectories(path); 454 } else { // a resource 455 makeDirectories(childloc.buildName(true, true, false)); 456 newResource(dir, childloc); 457 } 458 }); 459 dir.setCompleted(true); 460 n = dir; 461 return n; 462 } 463 464 Node handleResource(String name) { 465 Node n = null; 466 String locationPath = name.substring("/modules".length()); 467 ImageLocation resourceLoc = findLocation(locationPath); 468 if (resourceLoc != null) { 469 Directory dir = makeDirectories(resourceLoc.buildName(true, true, false)); 470 Resource res = newResource(dir, resourceLoc); 471 n = res; 472 } 473 return n; 474 } 475 476 String getBaseExt(ImageLocation loc) { 477 String base = loc.getBase(); 478 String ext = loc.getExtension(); 479 if (ext != null && !ext.isEmpty()) { 480 base = base + "." + ext; 481 } 482 return base; 483 } 484 485 synchronized Node findNode(String name) { 486 buildRootDirectory(); 487 Node n = nodes.get(name); 488 if (n == null || !n.isCompleted()) { 489 n = buildNode(name); 490 } 491 return n; 492 } 493 494 /** 495 * Returns the file attributes of the image file. 496 */ 497 BasicFileAttributes imageFileAttributes() { 498 BasicFileAttributes attrs = imageFileAttributes; 499 if (attrs == null) { 500 try { 501 Path file = getImagePath(); 502 attrs = Files.readAttributes(file, BasicFileAttributes.class); 503 } catch (IOException ioe) { 504 throw new UncheckedIOException(ioe); 505 } 506 imageFileAttributes = attrs; 507 } 508 return attrs; 509 } 510 511 Directory newDirectory(Directory parent, String name) { 512 Directory dir = Directory.create(parent, name, imageFileAttributes()); 513 nodes.put(dir.getName(), dir); 514 return dir; 515 } 516 517 Resource newResource(Directory parent, ImageLocation loc) { 518 Resource res = Resource.create(parent, loc, imageFileAttributes()); 519 nodes.put(res.getName(), res); 520 return res; 521 } 522 523 LinkNode newLinkNode(Directory dir, String name, Node link) { 524 LinkNode linkNode = LinkNode.create(dir, name, link); 525 nodes.put(linkNode.getName(), linkNode); 526 return linkNode; 527 } 528 529 Directory makeDirectories(String parent) { 530 Directory last = rootDir; 531 for (int offset = parent.indexOf('/', 1); 532 offset != -1; 533 offset = parent.indexOf('/', offset + 1)) { 534 String dir = parent.substring(0, offset); 535 last = makeDirectory(dir, last); 536 } 537 return makeDirectory(parent, last); 538 539 } 540 541 Directory makeDirectory(String dir, Directory last) { 542 Directory nextDir = (Directory) nodes.get(dir); 543 if (nextDir == null) { 544 nextDir = newDirectory(last, dir); 545 } 546 return nextDir; 547 } 548 549 byte[] getResource(Node node) throws IOException { 550 if (node.isResource()) { 551 return super.getResource(node.getLocation()); 552 } 553 throw new IOException("Not a resource: " + node); 554 } 555 556 byte[] getResource(Resource rs) throws IOException { 557 return super.getResource(rs.getLocation()); 558 } 559 } 560 561 // jimage file does not store directory structure. We build nodes 562 // using the "path" strings found in the jimage file. 563 // Node can be a directory or a resource 564 public abstract static class Node { 565 private static final int ROOT_DIR = 0b0000_0000_0000_0001; 566 private static final int PACKAGES_DIR = 0b0000_0000_0000_0010; 567 private static final int MODULES_DIR = 0b0000_0000_0000_0100; 568 569 private int flags; 570 private final String name; 571 private final BasicFileAttributes fileAttrs; 572 private boolean completed; 573 574 protected Node(String name, BasicFileAttributes fileAttrs) { 575 this.name = Objects.requireNonNull(name); 576 this.fileAttrs = Objects.requireNonNull(fileAttrs); 577 } 578 579 /** 580 * A node is completed when all its direct children have been built. 581 * 582 * @return 583 */ 584 public boolean isCompleted() { 585 return completed; 586 } 587 588 public void setCompleted(boolean completed) { 589 this.completed = completed; 590 } 591 592 public final void setIsRootDir() { 593 flags |= ROOT_DIR; 594 } 595 596 public final boolean isRootDir() { 597 return (flags & ROOT_DIR) != 0; 598 } 599 600 public final void setIsPackagesDir() { 601 flags |= PACKAGES_DIR; 602 } 603 604 public final boolean isPackagesDir() { 605 return (flags & PACKAGES_DIR) != 0; 606 } 607 608 public final void setIsModulesDir() { 609 flags |= MODULES_DIR; 610 } 611 612 public final boolean isModulesDir() { 613 return (flags & MODULES_DIR) != 0; 614 } 615 616 public final String getName() { 617 return name; 618 } 619 620 public final BasicFileAttributes getFileAttributes() { 621 return fileAttrs; 622 } 623 624 // resolve this Node (if this is a soft link, get underlying Node) 625 public final Node resolveLink() { 626 return resolveLink(false); 627 } 628 629 public Node resolveLink(boolean recursive) { 630 return this; 631 } 632 633 // is this a soft link Node? 634 public boolean isLink() { 635 return false; 636 } 637 638 public boolean isDirectory() { 639 return false; 640 } 641 642 public List<Node> getChildren() { 643 throw new IllegalArgumentException("not a directory: " + getNameString()); 644 } 645 646 public boolean isResource() { 647 return false; 648 } 649 650 public ImageLocation getLocation() { 651 throw new IllegalArgumentException("not a resource: " + getNameString()); 652 } 653 654 public long size() { 655 return 0L; 656 } 657 658 public long compressedSize() { 659 return 0L; 660 } 661 662 public String extension() { 663 return null; 664 } 665 666 public long contentOffset() { 667 return 0L; 668 } 669 670 public final FileTime creationTime() { 671 return fileAttrs.creationTime(); 672 } 673 674 public final FileTime lastAccessTime() { 675 return fileAttrs.lastAccessTime(); 676 } 677 678 public final FileTime lastModifiedTime() { 679 return fileAttrs.lastModifiedTime(); 680 } 681 682 public final String getNameString() { 683 return name; 684 } 685 686 @Override 687 public final String toString() { 688 return getNameString(); 689 } 690 691 @Override 692 public final int hashCode() { 693 return name.hashCode(); 694 } 695 696 @Override 697 public final boolean equals(Object other) { 698 if (this == other) { 699 return true; 700 } 701 702 if (other instanceof Node) { 703 return name.equals(((Node) other).name); 704 } 705 706 return false; 707 } 708 } 709 710 // directory node - directory has full path name without '/' at end. 711 static final class Directory extends Node { 712 private final List<Node> children; 713 714 private Directory(String name, BasicFileAttributes fileAttrs) { 715 super(name, fileAttrs); 716 children = new ArrayList<>(); 717 } 718 719 static Directory create(Directory parent, String name, BasicFileAttributes fileAttrs) { 720 Directory d = new Directory(name, fileAttrs); 721 if (parent != null) { 722 parent.addChild(d); 723 } 724 return d; 725 } 726 727 @Override 728 public boolean isDirectory() { 729 return true; 730 } 731 732 @Override 733 public List<Node> getChildren() { 734 return Collections.unmodifiableList(children); 735 } 736 737 void addChild(Node node) { 738 children.add(node); 739 } 740 741 public void walk(Consumer<? super Node> consumer) { 742 consumer.accept(this); 743 for ( Node child : children ) { 744 if (child.isDirectory()) { 745 ((Directory)child).walk(consumer); 746 } else { 747 consumer.accept(child); 748 } 749 } 750 } 751 } 752 753 // "resource" is .class or any other resource (compressed/uncompressed) in a jimage. 754 // full path of the resource is the "name" of the resource. 755 static class Resource extends Node { 756 private final ImageLocation loc; 757 758 private Resource(ImageLocation loc, BasicFileAttributes fileAttrs) { 759 super(loc.getFullName(true), fileAttrs); 760 this.loc = loc; 761 } 762 763 static Resource create(Directory parent, ImageLocation loc, BasicFileAttributes fileAttrs) { 764 Resource rs = new Resource(loc, fileAttrs); 765 parent.addChild(rs); 766 return rs; 767 } 768 769 @Override 770 public boolean isCompleted() { 771 return true; 772 } 773 774 @Override 775 public boolean isResource() { 776 return true; 777 } 778 779 @Override 780 public ImageLocation getLocation() { 781 return loc; 782 } 783 784 @Override 785 public long size() { 786 return loc.getUncompressedSize(); 787 } 788 789 @Override 790 public long compressedSize() { 791 return loc.getCompressedSize(); 792 } 793 794 @Override 795 public String extension() { 796 return loc.getExtension(); 797 } 798 799 @Override 800 public long contentOffset() { 801 return loc.getContentOffset(); 802 } 803 } 804 805 // represents a soft link to another Node 806 static class LinkNode extends Node { 807 private final Node link; 808 809 private LinkNode(String name, Node link) { 810 super(name, link.getFileAttributes()); 811 this.link = link; 812 } 813 814 static LinkNode create(Directory parent, String name, Node link) { 815 LinkNode ln = new LinkNode(name, link); 816 parent.addChild(ln); 817 return ln; 818 } 819 820 @Override 821 public boolean isCompleted() { 822 return true; 823 } 824 825 @Override 826 public Node resolveLink(boolean recursive) { 827 return (recursive && link instanceof LinkNode) ? ((LinkNode)link).resolveLink(true) : link; 828 } 829 830 @Override 831 public boolean isLink() { 832 return true; 833 } 834 } 835 }