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