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