Path: blob/master/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java
41159 views
/*1* Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package jdk.internal.jimage;2526import java.io.IOException;27import java.io.InputStream;28import java.io.UncheckedIOException;29import java.nio.ByteBuffer;30import java.nio.ByteOrder;31import java.nio.IntBuffer;32import java.nio.file.Files;33import java.nio.file.attribute.BasicFileAttributes;34import java.nio.file.attribute.FileTime;35import java.nio.file.Path;36import java.util.ArrayList;37import java.util.Collections;38import java.util.HashMap;39import java.util.HashSet;40import java.util.List;41import java.util.Map;42import java.util.Objects;43import java.util.Set;44import java.util.function.Consumer;4546/**47* @implNote This class needs to maintain JDK 8 source compatibility.48*49* It is used internally in the JDK to implement jimage/jrtfs access,50* but also compiled and delivered as part of the jrtfs.jar to support access51* to the jimage file provided by the shipped JDK by tools running on JDK 8.52*/53public final class ImageReader implements AutoCloseable {54private final SharedImageReader reader;5556private volatile boolean closed;5758private ImageReader(SharedImageReader reader) {59this.reader = reader;60}6162public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException {63Objects.requireNonNull(imagePath);64Objects.requireNonNull(byteOrder);6566return SharedImageReader.open(imagePath, byteOrder);67}6869public static ImageReader open(Path imagePath) throws IOException {70return open(imagePath, ByteOrder.nativeOrder());71}7273@Override74public void close() throws IOException {75if (closed) {76throw new IOException("image file already closed");77}78reader.close(this);79closed = true;80}8182private void ensureOpen() throws IOException {83if (closed) {84throw new IOException("image file closed");85}86}8788private void requireOpen() {89if (closed) {90throw new IllegalStateException("image file closed");91}92}9394// directory management interface95public Directory getRootDirectory() throws IOException {96ensureOpen();97return reader.getRootDirectory();98}99100101public Node findNode(String name) throws IOException {102ensureOpen();103return reader.findNode(name);104}105106public byte[] getResource(Node node) throws IOException {107ensureOpen();108return reader.getResource(node);109}110111public byte[] getResource(Resource rs) throws IOException {112ensureOpen();113return reader.getResource(rs);114}115116public ImageHeader getHeader() {117requireOpen();118return reader.getHeader();119}120121public static void releaseByteBuffer(ByteBuffer buffer) {122BasicImageReader.releaseByteBuffer(buffer);123}124125public String getName() {126requireOpen();127return reader.getName();128}129130public ByteOrder getByteOrder() {131requireOpen();132return reader.getByteOrder();133}134135public Path getImagePath() {136requireOpen();137return reader.getImagePath();138}139140public ImageStringsReader getStrings() {141requireOpen();142return reader.getStrings();143}144145public ImageLocation findLocation(String mn, String rn) {146requireOpen();147return reader.findLocation(mn, rn);148}149150public boolean verifyLocation(String mn, String rn) {151requireOpen();152return reader.verifyLocation(mn, rn);153}154155public ImageLocation findLocation(String name) {156requireOpen();157return reader.findLocation(name);158}159160public String[] getEntryNames() {161requireOpen();162return reader.getEntryNames();163}164165public String[] getModuleNames() {166requireOpen();167int off = "/modules/".length();168return reader.findNode("/modules")169.getChildren()170.stream()171.map(Node::getNameString)172.map(s -> s.substring(off, s.length()))173.toArray(String[]::new);174}175176public long[] getAttributes(int offset) {177requireOpen();178return reader.getAttributes(offset);179}180181public String getString(int offset) {182requireOpen();183return reader.getString(offset);184}185186public byte[] getResource(String name) {187requireOpen();188return reader.getResource(name);189}190191public byte[] getResource(ImageLocation loc) {192requireOpen();193return reader.getResource(loc);194}195196public ByteBuffer getResourceBuffer(ImageLocation loc) {197requireOpen();198return reader.getResourceBuffer(loc);199}200201public InputStream getResourceStream(ImageLocation loc) {202requireOpen();203return reader.getResourceStream(loc);204}205206private static final class SharedImageReader extends BasicImageReader {207static final int SIZE_OF_OFFSET = Integer.BYTES;208209static final Map<Path, SharedImageReader> OPEN_FILES = new HashMap<>();210211// List of openers for this shared image.212final Set<ImageReader> openers;213214// attributes of the .jimage file. jimage file does not contain215// attributes for the individual resources (yet). We use attributes216// of the jimage file itself (creation, modification, access times).217// Iniitalized lazily, see {@link #imageFileAttributes()}.218BasicFileAttributes imageFileAttributes;219220// directory management implementation221final HashMap<String, Node> nodes;222volatile Directory rootDir;223224Directory packagesDir;225Directory modulesDir;226227private SharedImageReader(Path imagePath, ByteOrder byteOrder) throws IOException {228super(imagePath, byteOrder);229this.openers = new HashSet<>();230this.nodes = new HashMap<>();231}232233public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException {234Objects.requireNonNull(imagePath);235Objects.requireNonNull(byteOrder);236237synchronized (OPEN_FILES) {238SharedImageReader reader = OPEN_FILES.get(imagePath);239240if (reader == null) {241// Will fail with an IOException if wrong byteOrder.242reader = new SharedImageReader(imagePath, byteOrder);243OPEN_FILES.put(imagePath, reader);244} else if (reader.getByteOrder() != byteOrder) {245throw new IOException("\"" + reader.getName() + "\" is not an image file");246}247248ImageReader image = new ImageReader(reader);249reader.openers.add(image);250251return image;252}253}254255public void close(ImageReader image) throws IOException {256Objects.requireNonNull(image);257258synchronized (OPEN_FILES) {259if (!openers.remove(image)) {260throw new IOException("image file already closed");261}262263if (openers.isEmpty()) {264close();265nodes.clear();266rootDir = null;267268if (!OPEN_FILES.remove(this.getImagePath(), this)) {269throw new IOException("image file not found in open list");270}271}272}273}274275void addOpener(ImageReader reader) {276synchronized (OPEN_FILES) {277openers.add(reader);278}279}280281boolean removeOpener(ImageReader reader) {282synchronized (OPEN_FILES) {283return openers.remove(reader);284}285}286287// directory management interface288Directory getRootDirectory() {289return buildRootDirectory();290}291292/**293* Lazily build a node from a name.294*/295synchronized Node buildNode(String name) {296Node n;297boolean isPackages = name.startsWith("/packages");298boolean isModules = !isPackages && name.startsWith("/modules");299300if (!(isModules || isPackages)) {301return null;302}303304ImageLocation loc = findLocation(name);305306if (loc != null) { // A sub tree node307if (isPackages) {308n = handlePackages(name, loc);309} else { // modules sub tree310n = handleModulesSubTree(name, loc);311}312} else { // Asking for a resource? /modules/java.base/java/lang/Object.class313if (isModules) {314n = handleResource(name);315} else {316// Possibly ask for /packages/java.lang/java.base317// although /packages/java.base not created318n = handleModuleLink(name);319}320}321return n;322}323324synchronized Directory buildRootDirectory() {325Directory root = rootDir; // volatile read326if (root != null) {327return root;328}329330root = newDirectory(null, "/");331root.setIsRootDir();332333// /packages dir334packagesDir = newDirectory(root, "/packages");335packagesDir.setIsPackagesDir();336337// /modules dir338modulesDir = newDirectory(root, "/modules");339modulesDir.setIsModulesDir();340341root.setCompleted(true);342return rootDir = root;343}344345/**346* To visit sub tree resources.347*/348interface LocationVisitor {349void visit(ImageLocation loc);350}351352void visitLocation(ImageLocation loc, LocationVisitor visitor) {353byte[] offsets = getResource(loc);354ByteBuffer buffer = ByteBuffer.wrap(offsets);355buffer.order(getByteOrder());356IntBuffer intBuffer = buffer.asIntBuffer();357for (int i = 0; i < offsets.length / SIZE_OF_OFFSET; i++) {358int offset = intBuffer.get(i);359ImageLocation pkgLoc = getLocation(offset);360visitor.visit(pkgLoc);361}362}363364void visitPackageLocation(ImageLocation loc) {365// Retrieve package name366String pkgName = getBaseExt(loc);367// Content is array of offsets in Strings table368byte[] stringsOffsets = getResource(loc);369ByteBuffer buffer = ByteBuffer.wrap(stringsOffsets);370buffer.order(getByteOrder());371IntBuffer intBuffer = buffer.asIntBuffer();372// For each module, create a link node.373for (int i = 0; i < stringsOffsets.length / SIZE_OF_OFFSET; i++) {374// skip empty state, useless.375intBuffer.get(i);376i++;377int offset = intBuffer.get(i);378String moduleName = getString(offset);379Node targetNode = findNode("/modules/" + moduleName);380if (targetNode != null) {381String pkgDirName = packagesDir.getName() + "/" + pkgName;382Directory pkgDir = (Directory) nodes.get(pkgDirName);383newLinkNode(pkgDir, pkgDir.getName() + "/" + moduleName, targetNode);384}385}386}387388Node handlePackages(String name, ImageLocation loc) {389long size = loc.getUncompressedSize();390Node n = null;391// Only possiblities are /packages, /packages/package/module392if (name.equals("/packages")) {393visitLocation(loc, (childloc) -> {394findNode(childloc.getFullName());395});396packagesDir.setCompleted(true);397n = packagesDir;398} else {399if (size != 0) { // children are offsets to module in StringsTable400String pkgName = getBaseExt(loc);401Directory pkgDir = newDirectory(packagesDir, packagesDir.getName() + "/" + pkgName);402visitPackageLocation(loc);403pkgDir.setCompleted(true);404n = pkgDir;405} else { // Link to module406String pkgName = loc.getParent();407String modName = getBaseExt(loc);408Node targetNode = findNode("/modules/" + modName);409if (targetNode != null) {410String pkgDirName = packagesDir.getName() + "/" + pkgName;411Directory pkgDir = (Directory) nodes.get(pkgDirName);412Node linkNode = newLinkNode(pkgDir, pkgDir.getName() + "/" + modName, targetNode);413n = linkNode;414}415}416}417return n;418}419420// Asking for /packages/package/module although421// /packages/<pkg>/ not yet created, need to create it422// prior to return the link to module node.423Node handleModuleLink(String name) {424// eg: unresolved /packages/package/module425// Build /packages/package node426Node ret = null;427String radical = "/packages/";428String path = name;429if (path.startsWith(radical)) {430int start = radical.length();431int pkgEnd = path.indexOf('/', start);432if (pkgEnd != -1) {433String pkg = path.substring(start, pkgEnd);434String pkgPath = radical + pkg;435Node n = findNode(pkgPath);436// If not found means that this is a symbolic link such as:437// /packages/java.util/java.base/java/util/Vector.class438// and will be done by a retry of the filesystem439for (Node child : n.getChildren()) {440if (child.name.equals(name)) {441ret = child;442break;443}444}445}446}447return ret;448}449450Node handleModulesSubTree(String name, ImageLocation loc) {451Node n;452assert (name.equals(loc.getFullName()));453Directory dir = makeDirectories(name);454visitLocation(loc, (childloc) -> {455String path = childloc.getFullName();456if (path.startsWith("/modules")) { // a package457makeDirectories(path);458} else { // a resource459makeDirectories(childloc.buildName(true, true, false));460newResource(dir, childloc);461}462});463dir.setCompleted(true);464n = dir;465return n;466}467468Node handleResource(String name) {469Node n = null;470if (!name.startsWith("/modules/")) {471return null;472}473// Make sure that the thing that follows "/modules/" is a module name.474int moduleEndIndex = name.indexOf('/', "/modules/".length());475if (moduleEndIndex == -1) {476return null;477}478ImageLocation moduleLoc = findLocation(name.substring(0, moduleEndIndex));479if (moduleLoc == null || moduleLoc.getModuleOffset() == 0) {480return null;481}482483String locationPath = name.substring("/modules".length());484ImageLocation resourceLoc = findLocation(locationPath);485if (resourceLoc != null) {486Directory dir = makeDirectories(resourceLoc.buildName(true, true, false));487Resource res = newResource(dir, resourceLoc);488n = res;489}490return n;491}492493String getBaseExt(ImageLocation loc) {494String base = loc.getBase();495String ext = loc.getExtension();496if (ext != null && !ext.isEmpty()) {497base = base + "." + ext;498}499return base;500}501502synchronized Node findNode(String name) {503buildRootDirectory();504Node n = nodes.get(name);505if (n == null || !n.isCompleted()) {506n = buildNode(name);507}508return n;509}510511/**512* Returns the file attributes of the image file.513*/514BasicFileAttributes imageFileAttributes() {515BasicFileAttributes attrs = imageFileAttributes;516if (attrs == null) {517try {518Path file = getImagePath();519attrs = Files.readAttributes(file, BasicFileAttributes.class);520} catch (IOException ioe) {521throw new UncheckedIOException(ioe);522}523imageFileAttributes = attrs;524}525return attrs;526}527528Directory newDirectory(Directory parent, String name) {529Directory dir = Directory.create(parent, name, imageFileAttributes());530nodes.put(dir.getName(), dir);531return dir;532}533534Resource newResource(Directory parent, ImageLocation loc) {535Resource res = Resource.create(parent, loc, imageFileAttributes());536nodes.put(res.getName(), res);537return res;538}539540LinkNode newLinkNode(Directory dir, String name, Node link) {541LinkNode linkNode = LinkNode.create(dir, name, link);542nodes.put(linkNode.getName(), linkNode);543return linkNode;544}545546Directory makeDirectories(String parent) {547Directory last = rootDir;548for (int offset = parent.indexOf('/', 1);549offset != -1;550offset = parent.indexOf('/', offset + 1)) {551String dir = parent.substring(0, offset);552last = makeDirectory(dir, last);553}554return makeDirectory(parent, last);555556}557558Directory makeDirectory(String dir, Directory last) {559Directory nextDir = (Directory) nodes.get(dir);560if (nextDir == null) {561nextDir = newDirectory(last, dir);562}563return nextDir;564}565566byte[] getResource(Node node) throws IOException {567if (node.isResource()) {568return super.getResource(node.getLocation());569}570throw new IOException("Not a resource: " + node);571}572573byte[] getResource(Resource rs) throws IOException {574return super.getResource(rs.getLocation());575}576}577578// jimage file does not store directory structure. We build nodes579// using the "path" strings found in the jimage file.580// Node can be a directory or a resource581public abstract static class Node {582private static final int ROOT_DIR = 0b0000_0000_0000_0001;583private static final int PACKAGES_DIR = 0b0000_0000_0000_0010;584private static final int MODULES_DIR = 0b0000_0000_0000_0100;585586private int flags;587private final String name;588private final BasicFileAttributes fileAttrs;589private boolean completed;590591protected Node(String name, BasicFileAttributes fileAttrs) {592this.name = Objects.requireNonNull(name);593this.fileAttrs = Objects.requireNonNull(fileAttrs);594}595596/**597* A node is completed when all its direct children have been built.598*599* @return600*/601public boolean isCompleted() {602return completed;603}604605public void setCompleted(boolean completed) {606this.completed = completed;607}608609public final void setIsRootDir() {610flags |= ROOT_DIR;611}612613public final boolean isRootDir() {614return (flags & ROOT_DIR) != 0;615}616617public final void setIsPackagesDir() {618flags |= PACKAGES_DIR;619}620621public final boolean isPackagesDir() {622return (flags & PACKAGES_DIR) != 0;623}624625public final void setIsModulesDir() {626flags |= MODULES_DIR;627}628629public final boolean isModulesDir() {630return (flags & MODULES_DIR) != 0;631}632633public final String getName() {634return name;635}636637public final BasicFileAttributes getFileAttributes() {638return fileAttrs;639}640641// resolve this Node (if this is a soft link, get underlying Node)642public final Node resolveLink() {643return resolveLink(false);644}645646public Node resolveLink(boolean recursive) {647return this;648}649650// is this a soft link Node?651public boolean isLink() {652return false;653}654655public boolean isDirectory() {656return false;657}658659public List<Node> getChildren() {660throw new IllegalArgumentException("not a directory: " + getNameString());661}662663public boolean isResource() {664return false;665}666667public ImageLocation getLocation() {668throw new IllegalArgumentException("not a resource: " + getNameString());669}670671public long size() {672return 0L;673}674675public long compressedSize() {676return 0L;677}678679public String extension() {680return null;681}682683public long contentOffset() {684return 0L;685}686687public final FileTime creationTime() {688return fileAttrs.creationTime();689}690691public final FileTime lastAccessTime() {692return fileAttrs.lastAccessTime();693}694695public final FileTime lastModifiedTime() {696return fileAttrs.lastModifiedTime();697}698699public final String getNameString() {700return name;701}702703@Override704public final String toString() {705return getNameString();706}707708@Override709public final int hashCode() {710return name.hashCode();711}712713@Override714public final boolean equals(Object other) {715if (this == other) {716return true;717}718719if (other instanceof Node) {720return name.equals(((Node) other).name);721}722723return false;724}725}726727// directory node - directory has full path name without '/' at end.728static final class Directory extends Node {729private final List<Node> children;730731private Directory(String name, BasicFileAttributes fileAttrs) {732super(name, fileAttrs);733children = new ArrayList<>();734}735736static Directory create(Directory parent, String name, BasicFileAttributes fileAttrs) {737Directory d = new Directory(name, fileAttrs);738if (parent != null) {739parent.addChild(d);740}741return d;742}743744@Override745public boolean isDirectory() {746return true;747}748749@Override750public List<Node> getChildren() {751return Collections.unmodifiableList(children);752}753754void addChild(Node node) {755children.add(node);756}757758public void walk(Consumer<? super Node> consumer) {759consumer.accept(this);760for (Node child : children) {761if (child.isDirectory()) {762((Directory)child).walk(consumer);763} else {764consumer.accept(child);765}766}767}768}769770// "resource" is .class or any other resource (compressed/uncompressed) in a jimage.771// full path of the resource is the "name" of the resource.772static class Resource extends Node {773private final ImageLocation loc;774775private Resource(ImageLocation loc, BasicFileAttributes fileAttrs) {776super(loc.getFullName(true), fileAttrs);777this.loc = loc;778}779780static Resource create(Directory parent, ImageLocation loc, BasicFileAttributes fileAttrs) {781Resource rs = new Resource(loc, fileAttrs);782parent.addChild(rs);783return rs;784}785786@Override787public boolean isCompleted() {788return true;789}790791@Override792public boolean isResource() {793return true;794}795796@Override797public ImageLocation getLocation() {798return loc;799}800801@Override802public long size() {803return loc.getUncompressedSize();804}805806@Override807public long compressedSize() {808return loc.getCompressedSize();809}810811@Override812public String extension() {813return loc.getExtension();814}815816@Override817public long contentOffset() {818return loc.getContentOffset();819}820}821822// represents a soft link to another Node823static class LinkNode extends Node {824private final Node link;825826private LinkNode(String name, Node link) {827super(name, link.getFileAttributes());828this.link = link;829}830831static LinkNode create(Directory parent, String name, Node link) {832LinkNode ln = new LinkNode(name, link);833parent.addChild(ln);834return ln;835}836837@Override838public boolean isCompleted() {839return true;840}841842@Override843public Node resolveLink(boolean recursive) {844return (recursive && link instanceof LinkNode) ? ((LinkNode)link).resolveLink(true) : link;845}846847@Override848public boolean isLink() {849return true;850}851}852}853854855