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         if (!node.isDirectory()) {
 162             throw new NotDirectoryException(getString(path));
 163         }
 164 
 165         if (node.isRootDir()) {
 166             return rootDirIterator(path, childPrefix);
 167         } else if (node.isModulesDir()) {
 168             return modulesDirIterator(path, childPrefix);
 169         } else if (node.isPackagesDir()) {
 170             return packagesDirIterator(path, childPrefix);
 171         }
 172 
 173         return nodesToIterator(toJrtPath(path), ni.symLink, childPrefix, node.getChildren());
 174     }
 175 
 176     @Override
 177     byte[] getFileContent(byte[] path) throws IOException {
 178         final NodeAndImage ni = checkResource(path);
 179         return ni.getResource();
 180     }
 181 
 182     // Implementation details below this point
 183     // clean up this file system - called from finalize and close
 184     private void cleanup() throws IOException {
 185         if (!isOpen) {
 186             return;
 187         }
 188 
 189         synchronized (this) {
 190             isOpen = false;
 191 
 192             // close all image reader and null out
 193             bootImage.close();
 194             bootImage = null;
 195         }
 196     }
 197 
 198     private static class NodeAndImage {
 199 
 200         final Node node;
 201         final ImageReader image;
 202         // was there a symlink somewhere while resolving this Node from name?
 203         final boolean symLink;
 204 
 205         NodeAndImage(Node node, ImageReader image, boolean symLink) {
 206             this.node = node;
 207             this.image = image;
 208             this.symLink = symLink;
 209         }
 210 
 211         byte[] getResource() throws IOException {
 212             return image.getResource(node);
 213         }
 214     }
 215 
 216     private NodeAndImage lookup(byte[] path) {
 217         return lookup(path, false);
 218     }
 219 
 220     private NodeAndImage lookup(byte[] path, boolean symLink) {
 221         ImageReader image = bootImage;
 222         Node node;
 223         try {
 224             node = bootImage.findNode(path);
 225         } catch (RuntimeException re) {
 226             throw new InvalidPathException(getString(path), re.toString());
 227         }
 228         return node != null ? new NodeAndImage(node, image, symLink) : null;
 229     }
 230 
 231     private NodeAndImage lookupSymbolic(byte[] path) {
 232         for (int i = 1; i < path.length; i++) {
 233             if (path[i] == (byte) '/') {
 234                 byte[] prefix = Arrays.copyOfRange(path, 0, i);
 235                 NodeAndImage ni = lookup(prefix, true);
 236                 if (ni == null) {
 237                     break;
 238                 }
 239 
 240                 if (ni.node.isLink()) {
 241                     Node link = ni.node.resolveLink(true);
 242                     // resolved symbolic path concatenated to the rest of the path
 243                     UTF8String resPath = link.getName().concat(new UTF8String(path, i));
 244                     byte[] resPathBytes = resPath.getBytesCopy();
 245                     ni = lookup(resPathBytes, true);
 246                     return ni != null ? ni : lookupSymbolic(resPathBytes);
 247                 }
 248             }
 249         }
 250 
 251         return null;
 252     }
 253 
 254     private NodeAndImage findNode(byte[] path) throws IOException {
 255         NodeAndImage ni = lookup(path);
 256         if (ni == null) {
 257             ni = lookupSymbolic(path);
 258             if (ni == null) {
 259                 throw new NoSuchFileException(getString(path));
 260             }
 261         }
 262         return ni;
 263     }
 264 
 265     private NodeAndImage checkNode(byte[] path) throws IOException {
 266         ensureOpen();
 267         return findNode(path);
 268     }
 269 
 270     private NodeAndImage checkResource(byte[] path) throws IOException {
 271         NodeAndImage ni = checkNode(path);
 272         if (ni.node.isDirectory()) {
 273             throw new FileSystemException(getString(path) + " is a directory");
 274         }
 275 
 276         assert ni.node.isResource() : "resource node expected here";
 277         return ni;
 278     }
 279 
 280     private JrtPath toJrtPath(String path) {
 281         return toJrtPath(getBytes(path));
 282     }
 283 
 284     private JrtPath toJrtPath(byte[] path) {
 285         return new JrtPath(this, path);
 286     }
 287 
 288     private static final int MODULES_LEN = "/modules/".length();
 289     private static final int PACKAGES_LEN = "/packages/".length();
 290 
 291     private Iterator<Path> nodesToIterator(Path path, boolean symLink, String childPrefix, List<Node> childNodes) {
 292         // Function that maps name -> Path (adding prefix if needed)
 293         Function<String, Path> prefixHandler = childPrefix == null
 294                 ? childStr -> toJrtPath(childStr)
 295                 : childStr -> toJrtPath(childPrefix + childStr.substring(1));
 296 
 297         // function to map Node->name
 298         Function<Node, String> nodeHandler;
 299         if (symLink) {
 300             // There was a symlink in the parent dir! We need to make sure that the child paths
 301             // use the appropriate parent prefix in the name.
 302             String parentStr = path.toAbsolutePath().toString();
 303             // "/packages/java.lang/java.base/java/" -> "/packages/java.lang/"
 304             String parentPrefix = parentStr.substring(0, 1 + parentStr.indexOf('/', PACKAGES_LEN));
 305 
 306             nodeHandler = node -> parentPrefix + node.getNameString().substring(MODULES_LEN);
 307         } else {
 308             nodeHandler = Node::getNameString;
 309         }
 310 
 311         return childNodes.stream().map(nodeHandler.andThen(prefixHandler)).collect(toList()).iterator();
 312     }
 313 
 314     private List<Node> rootChildren;
 315 
 316     private synchronized void initRootChildren(byte[] path) {
 317         if (rootChildren == null) {
 318             rootChildren = new ArrayList<>();
 319             rootChildren.addAll(bootImage.findNode(path).getChildren());
 320         }
 321     }
 322 
 323     private Iterator<Path> rootDirIterator(byte[] path, String childPrefix) throws IOException {
 324         initRootChildren(path);
 325         return nodesToIterator(rootPath, false, childPrefix, rootChildren);
 326     }
 327 
 328     private List<Node> modulesChildren;
 329 
 330     private synchronized void initModulesChildren(byte[] path) {
 331         if (modulesChildren == null) {
 332             modulesChildren = new ArrayList<>();
 333             modulesChildren.addAll(bootImage.findNode(path).getChildren());
 334         }
 335     }
 336 
 337     private Iterator<Path> modulesDirIterator(byte[] path, String childPrefix) throws IOException {
 338         initModulesChildren(path);
 339         return nodesToIterator(new JrtPath(this, path), false, childPrefix, modulesChildren);
 340     }
 341 
 342     private List<Node> packagesChildren;
 343 
 344     private synchronized void initPackagesChildren(byte[] path) {
 345         if (packagesChildren == null) {
 346             packagesChildren = new ArrayList<>();
 347             packagesChildren.addAll(bootImage.findNode(path).getChildren());
 348         }
 349     }
 350 
 351     private Iterator<Path> packagesDirIterator(byte[] path, String childPrefix) throws IOException {
 352         initPackagesChildren(path);
 353         return nodesToIterator(new JrtPath(this, path), false, childPrefix, packagesChildren);
 354     }
 355 }
--- EOF ---