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