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 */
25package jdk.internal.jimage;
26
27import java.io.IOException;
28import java.io.InputStream;
29import java.io.UncheckedIOException;
30import java.nio.ByteBuffer;
31import java.nio.ByteOrder;
32import java.nio.IntBuffer;
33import java.nio.file.Files;
34import java.nio.file.attribute.BasicFileAttributes;
35import java.nio.file.attribute.FileTime;
36import java.nio.file.Path;
37import java.util.ArrayList;
38import java.util.Collections;
39import java.util.HashMap;
40import java.util.HashSet;
41import java.util.List;
42import java.util.Map;
43import java.util.Objects;
44import java.util.Set;
45import 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 */
54public 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}
836