1 /*
   2  * Copyright (c) 2014, 2015, 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.jrtfs;
  26 
  27 import java.io.IOException;
  28 import java.nio.file.LinkOption;
  29 import java.nio.file.FileSystemException;
  30 import java.nio.file.InvalidPathException;
  31 import java.nio.file.NoSuchFileException;
  32 import java.nio.file.NotDirectoryException;
  33 import java.nio.file.Path;
  34 import java.util.ArrayList;
  35 import java.util.Arrays;
  36 import java.util.Iterator;
  37 import java.util.List;
  38 import java.util.Map;
  39 import java.util.function.Function;
  40 import static java.util.stream.Collectors.toList;
  41 import jdk.internal.jimage.ImageReader;
  42 import jdk.internal.jimage.ImageReader.Node;
  43 import jdk.internal.jimage.UTF8String;
  44 
  45 /**
  46  * jrt file system implementation built on System jimage files.
  47  */
  48 class JrtFileSystem extends AbstractJrtFileSystem {
  49 
  50     // System image reader
  51     private ImageReader bootImage;
  52     // root path
  53     private final JrtPath rootPath;
  54     private volatile boolean isOpen;
  55 
  56     // open a .jimage and build directory structure
  57     private static ImageReader openImage(Path path) throws IOException {
  58         ImageReader image = ImageReader.open(path.toString());
  59         image.getRootDirectory();
  60         return image;
  61     }
  62 
  63     JrtFileSystem(JrtFileSystemProvider provider,
  64             Map<String, ?> env)
  65             throws IOException {
  66         super(provider, env);
  67         checkExists(SystemImages.bootImagePath);
  68 
  69         // open image file
  70         this.bootImage = openImage(SystemImages.bootImagePath);
  71 
  72         byte[] root = new byte[]{'/'};
  73         rootPath = new JrtPath(this, root);
  74         isOpen = true;
  75     }
  76 
  77     // FileSystem method implementations
  78     @Override
  79     public boolean isOpen() {
  80         return isOpen;
  81     }
  82 
  83     @Override
  84     public void close() throws IOException {
  85         cleanup();
  86     }
  87 
  88     @Override
  89     protected void finalize() throws Throwable {
  90         try {
  91             cleanup();
  92         } catch (IOException ignored) {
  93         }
  94         super.finalize();
  95     }
  96 
  97     // AbstractJrtFileSystem method implementations
  98     @Override
  99     JrtPath getRootPath() {
 100         return rootPath;
 101     }
 102 
 103     @Override
 104     boolean isSameFile(AbstractJrtPath p1, AbstractJrtPath p2) throws IOException {
 105         ensureOpen();
 106         NodeAndImage n1 = findNode(p1.getName());
 107         NodeAndImage n2 = findNode(p2.getName());
 108         return n1.node.equals(n2.node);
 109     }
 110 
 111     @Override
 112     boolean isLink(AbstractJrtPath jrtPath) throws IOException {
 113         return checkNode(jrtPath.getName()).node.isLink();
 114     }
 115 
 116     @Override
 117     AbstractJrtPath resolveLink(AbstractJrtPath jrtPath) throws IOException {
 118         NodeAndImage ni = checkNode(jrtPath.getName());
 119         if (ni.node.isLink()) {
 120             Node node = ni.node.resolveLink();
 121             return toJrtPath(node.getName().getBytesCopy());
 122         }
 123 
 124         return jrtPath;
 125     }
 126 
 127     @Override
 128     JrtFileAttributes getFileAttributes(byte[] path, LinkOption... options)
 129             throws IOException {
 130         NodeAndImage ni = checkNode(path);
 131         if (ni.node.isLink() && followLinks(options)) {
 132             return new JrtFileAttributes(ni.node.resolveLink(true));
 133         }
 134         return new JrtFileAttributes(ni.node);
 135     }
 136 
 137     @Override
 138     boolean exists(byte[] path) throws IOException {
 139         try {
 140             checkNode(path);
 141         } catch (NoSuchFileException exp) {
 142             return false;
 143         }
 144         return true;
 145     }
 146 
 147     @Override
 148     boolean isDirectory(byte[] path, boolean resolveLinks)
 149             throws IOException {
 150         NodeAndImage ni = checkNode(path);
 151         return resolveLinks && ni.node.isLink()
 152                 ? ni.node.resolveLink(true).isDirectory()
 153                 : ni.node.isDirectory();
 154     }
 155 
 156     @Override
 157     Iterator<Path> iteratorOf(byte[] path, String childPrefix)
 158             throws IOException {
 159         NodeAndImage ni = checkNode(path);
 160         Node node = ni.node.resolveLink(true);
 161 
 162         if (!node.isDirectory()) {
 163             throw new NotDirectoryException(getString(path));
 164         }
 165 
 166         if (node.isRootDir()) {
 167             return rootDirIterator(path, childPrefix);
 168         } else if (node.isModulesDir()) {
 169             return modulesDirIterator(path, childPrefix);
 170         } else if (node.isPackagesDir()) {
 171             return packagesDirIterator(path, childPrefix);
 172         }
 173 
 174         return nodesToIterator(toJrtPath(path), childPrefix, node.getChildren());
 175     }
 176 
 177     @Override
 178     byte[] getFileContent(byte[] path) throws IOException {
 179         final NodeAndImage ni = checkResource(path);
 180         return ni.getResource();
 181     }
 182 
 183     // Implementation details below this point
 184     // clean up this file system - called from finalize and close
 185     private void cleanup() throws IOException {
 186         if (!isOpen) {
 187             return;
 188         }
 189 
 190         synchronized (this) {
 191             isOpen = false;
 192 
 193             // close all image reader and null out
 194             bootImage.close();
 195             bootImage = null;
 196         }
 197     }
 198 
 199     private static class NodeAndImage {
 200 
 201         final Node node;
 202         final ImageReader image;
 203 
 204         NodeAndImage(Node node, ImageReader image) {
 205             this.node = node;
 206             this.image = image;
 207         }
 208 
 209         byte[] getResource() throws IOException {
 210             return image.getResource(node);
 211         }
 212     }
 213 
 214     private NodeAndImage lookup(byte[] path) {
 215         ImageReader image = bootImage;
 216         Node node;
 217         try {
 218             node = bootImage.findNode(path);
 219         } catch (RuntimeException re) {
 220             throw new InvalidPathException(getString(path), re.toString());
 221         }
 222         return node != null ? new NodeAndImage(node, image) : null;
 223     }
 224 
 225     private NodeAndImage lookupSymbolic(byte[] path) {
 226         for (int i = 1; i < path.length; i++) {
 227             if (path[i] == (byte) '/') {
 228                 byte[] prefix = Arrays.copyOfRange(path, 0, i);
 229                 NodeAndImage ni = lookup(prefix);
 230                 if (ni == null) {
 231                     break;
 232                 }
 233 
 234                 if (ni.node.isLink()) {
 235                     Node link = ni.node.resolveLink(true);
 236                     // resolved symbolic path concatenated to the rest of the path
 237                     UTF8String resPath = link.getName().concat(new UTF8String(path, i));
 238                     byte[] resPathBytes = resPath.getBytesCopy();
 239                     ni = lookup(resPathBytes);
 240                     return ni != null ? ni : lookupSymbolic(resPathBytes);
 241                 }
 242             }
 243         }
 244 
 245         return null;
 246     }
 247 
 248     private NodeAndImage findNode(byte[] path) throws IOException {
 249         NodeAndImage ni = lookup(path);
 250         if (ni == null) {
 251             ni = lookupSymbolic(path);
 252             if (ni == null) {
 253                 throw new NoSuchFileException(getString(path));
 254             }
 255         }
 256         return ni;
 257     }
 258 
 259     private NodeAndImage checkNode(byte[] path) throws IOException {
 260         ensureOpen();
 261         return findNode(path);
 262     }
 263 
 264     private NodeAndImage checkResource(byte[] path) throws IOException {
 265         NodeAndImage ni = checkNode(path);
 266         if (ni.node.isDirectory()) {
 267             throw new FileSystemException(getString(path) + " is a directory");
 268         }
 269 
 270         assert ni.node.isResource() : "resource node expected here";
 271         return ni;
 272     }
 273 
 274     private JrtPath toJrtPath(String path) {
 275         return toJrtPath(getBytes(path));
 276     }
 277 
 278     private JrtPath toJrtPath(byte[] path) {
 279         return new JrtPath(this, path);
 280     }
 281 
 282     private Iterator<Path> nodesToIterator(Path path, String childPrefix, List<Node> childNodes) {
 283         Function<Node, Path> f = childPrefix == null
 284                 ? child -> toJrtPath(child.getNameString())
 285                 : child -> toJrtPath(childPrefix + child.getNameString().substring(1));
 286         return childNodes.stream().map(f).collect(toList()).iterator();
 287     }
 288 
 289     private List<Node> rootChildren;
 290 
 291     private synchronized void initRootChildren(byte[] path) {
 292         if (rootChildren == null) {
 293             rootChildren = new ArrayList<>();
 294             rootChildren.addAll(bootImage.findNode(path).getChildren());
 295         }
 296     }
 297 
 298     private Iterator<Path> rootDirIterator(byte[] path, String childPrefix) throws IOException {
 299         initRootChildren(path);
 300         return nodesToIterator(rootPath, childPrefix, rootChildren);
 301     }
 302 
 303     private List<Node> modulesChildren;
 304 
 305     private synchronized void initModulesChildren(byte[] path) {
 306         if (modulesChildren == null) {
 307             modulesChildren = new ArrayList<>();
 308             modulesChildren.addAll(bootImage.findNode(path).getChildren());
 309         }
 310     }
 311 
 312     private Iterator<Path> modulesDirIterator(byte[] path, String childPrefix) throws IOException {
 313         initModulesChildren(path);
 314         return nodesToIterator(new JrtPath(this, path), childPrefix, modulesChildren);
 315     }
 316 
 317     private List<Node> packagesChildren;
 318 
 319     private synchronized void initPackagesChildren(byte[] path) {
 320         if (packagesChildren == null) {
 321             packagesChildren = new ArrayList<>();
 322             packagesChildren.addAll(bootImage.findNode(path).getChildren());
 323         }
 324     }
 325 
 326     private Iterator<Path> packagesDirIterator(byte[] path, String childPrefix) throws IOException {
 327         initPackagesChildren(path);
 328         return nodesToIterator(new JrtPath(this, path), childPrefix, packagesChildren);
 329     }
 330 }