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.ByteArrayInputStream;
  28 import java.io.IOException;
  29 import java.io.InputStream;
  30 import java.io.OutputStream;
  31 import java.nio.ByteBuffer;
  32 import java.nio.channels.Channels;
  33 import java.nio.channels.FileChannel;
  34 import java.nio.channels.NonWritableChannelException;
  35 import java.nio.channels.ReadableByteChannel;
  36 import java.nio.channels.SeekableByteChannel;
  37 import java.nio.file.ClosedFileSystemException;
  38 import java.nio.file.CopyOption;
  39 import java.nio.file.DirectoryStream;
  40 import java.nio.file.FileStore;
  41 import java.nio.file.FileSystem;
  42 import java.nio.file.FileSystemException;
  43 import java.nio.file.InvalidPathException;
  44 import java.nio.file.LinkOption;
  45 import java.nio.file.NoSuchFileException;
  46 import java.nio.file.NotDirectoryException;
  47 import java.nio.file.OpenOption;
  48 import java.nio.file.Path;
  49 import java.nio.file.PathMatcher;
  50 import java.nio.file.ReadOnlyFileSystemException;
  51 import java.nio.file.StandardOpenOption;
  52 import java.nio.file.WatchService;
  53 import java.nio.file.attribute.FileAttribute;
  54 import java.nio.file.attribute.FileTime;
  55 import java.nio.file.attribute.UserPrincipalLookupService;
  56 import java.nio.file.spi.FileSystemProvider;
  57 import java.util.ArrayList;
  58 import java.util.Arrays;
  59 import java.util.Collections;
  60 import java.util.HashSet;
  61 import java.util.Iterator;
  62 import java.util.Map;
  63 import java.util.Objects;
  64 import java.util.Set;
  65 import java.util.regex.Pattern;
  66 import jdk.internal.jimage.ImageReader.Node;
  67 import static java.util.stream.Collectors.toList;
  68 
  69 /**
  70  * jrt file system implementation built on System jimage files.
  71  *
  72  * @implNote This class needs to maintain JDK 8 source compatibility.
  73  *
  74  * It is used internally in the JDK to implement jimage/jrtfs access,
  75  * but also compiled and delivered as part of the jrtfs.jar to support access
  76  * to the jimage file provided by the shipped JDK by tools running on JDK 8.
  77  */
  78 class JrtFileSystem extends FileSystem {
  79 
  80     private final JrtFileSystemProvider provider;
  81     private final JrtPath rootPath = new JrtPath(this, "/");
  82     private volatile boolean isOpen;
  83     private volatile boolean isClosable;
  84     private SystemImage image;
  85 
  86     JrtFileSystem(JrtFileSystemProvider provider, Map<String, ?> env)
  87             throws IOException
  88     {
  89         this.provider = provider;
  90         this.image = SystemImage.open();  // open image file
  91         this.isOpen = true;
  92         this.isClosable = env != null;
  93     }
  94 
  95     // FileSystem method implementations
  96     @Override
  97     public boolean isOpen() {
  98         return isOpen;
  99     }
 100 
 101     @Override
 102     public void close() throws IOException {
 103         if (!isClosable)
 104             throw new UnsupportedOperationException();
 105         cleanup();
 106     }
 107 
 108     @Override
 109     protected void finalize() throws Throwable {
 110         try {
 111             cleanup();
 112         } catch (IOException ignored) {}
 113     }
 114 
 115     @Override
 116     public FileSystemProvider provider() {
 117         return provider;
 118     }
 119 
 120     @Override
 121     public Iterable<Path> getRootDirectories() {
 122         ArrayList<Path> dirs = new ArrayList<>();
 123         dirs.add(getRootPath());
 124         return dirs;
 125     }
 126 
 127     @Override
 128     public JrtPath getPath(String first, String... more) {
 129         if (more.length == 0) {
 130             return new JrtPath(this, first);
 131         }
 132         StringBuilder sb = new StringBuilder();
 133         sb.append(first);
 134         for (String path : more) {
 135             if (path.length() > 0) {
 136                 if (sb.length() > 0) {
 137                     sb.append('/');
 138                 }
 139                 sb.append(path);
 140             }
 141         }
 142         return new JrtPath(this, sb.toString());
 143     }
 144 
 145     @Override
 146     public final boolean isReadOnly() {
 147         return true;
 148     }
 149 
 150     @Override
 151     public final UserPrincipalLookupService getUserPrincipalLookupService() {
 152         throw new UnsupportedOperationException();
 153     }
 154 
 155     @Override
 156     public final WatchService newWatchService() {
 157         throw new UnsupportedOperationException();
 158     }
 159 
 160     @Override
 161     public final Iterable<FileStore> getFileStores() {
 162         ArrayList<FileStore> list = new ArrayList<>(1);
 163         list.add(getFileStore(getRootPath()));
 164         return list;
 165     }
 166 
 167     private static final Set<String> supportedFileAttributeViews
 168             = Collections.unmodifiableSet(
 169                     new HashSet<String>(Arrays.asList("basic", "jrt")));
 170 
 171     @Override
 172     public final Set<String> supportedFileAttributeViews() {
 173         return supportedFileAttributeViews;
 174     }
 175 
 176     @Override
 177     public final String toString() {
 178         return "jrt:/";
 179     }
 180 
 181     @Override
 182     public final String getSeparator() {
 183         return "/";
 184     }
 185 
 186     @Override
 187     public PathMatcher getPathMatcher(String syntaxAndInput) {
 188         int pos = syntaxAndInput.indexOf(':');
 189         if (pos <= 0 || pos == syntaxAndInput.length()) {
 190             throw new IllegalArgumentException();
 191         }
 192         String syntax = syntaxAndInput.substring(0, pos);
 193         String input = syntaxAndInput.substring(pos + 1);
 194         String expr;
 195         if (syntax.equalsIgnoreCase("glob")) {
 196             expr = JrtUtils.toRegexPattern(input);
 197         } else if (syntax.equalsIgnoreCase("regex")) {
 198             expr = input;
 199         } else {
 200                 throw new UnsupportedOperationException("Syntax '" + syntax
 201                         + "' not recognized");
 202         }
 203         // return matcher
 204         final Pattern pattern = Pattern.compile(expr);
 205         return (Path path) -> pattern.matcher(path.toString()).matches();
 206     }
 207 
 208     JrtPath resolveLink(JrtPath path) throws IOException {
 209         Node node = checkNode(path);
 210         if (node.isLink()) {
 211             node = node.resolveLink();
 212             return new JrtPath(this, node.getName());  // TBD, normalized?
 213         }
 214         return path;
 215     }
 216 
 217     JrtFileAttributes getFileAttributes(JrtPath path, LinkOption... options)
 218             throws IOException {
 219         Node node = checkNode(path);
 220         if (node.isLink() && followLinks(options)) {
 221             return new JrtFileAttributes(node.resolveLink(true));
 222         }
 223         return new JrtFileAttributes(node);
 224     }
 225 
 226     /**
 227      * returns the list of child paths of the given directory "path"
 228      *
 229      * @param path name of the directory whose content is listed
 230      * @return iterator for child paths of the given directory path
 231      */
 232     Iterator<Path> iteratorOf(JrtPath path, DirectoryStream.Filter<? super Path> filter)
 233             throws IOException {
 234         Node node = checkNode(path).resolveLink(true);
 235         if (!node.isDirectory()) {
 236             throw new NotDirectoryException(path.getName());
 237         }
 238         if (filter == null) {
 239             return node.getChildren()
 240                        .stream()
 241                        .map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName())))
 242                        .iterator();
 243         }
 244         return node.getChildren()
 245                    .stream()
 246                    .map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName())))
 247                    .filter(p ->  { try { return filter.accept(p);
 248                                    } catch (IOException x) {}
 249                                    return false;
 250                                   })
 251                    .iterator();
 252     }
 253 
 254     // returns the content of the file resource specified by the path
 255     byte[] getFileContent(JrtPath path) throws IOException {
 256         Node node = checkNode(path);
 257         if (node.isDirectory()) {
 258             throw new FileSystemException(path + " is a directory");
 259         }
 260         //assert node.isResource() : "resource node expected here";
 261         return image.getResource(node);
 262     }
 263 
 264     /////////////// Implementation details below this point //////////
 265 
 266     // static utility methods
 267     static ReadOnlyFileSystemException readOnly() {
 268         return new ReadOnlyFileSystemException();
 269     }
 270 
 271     // do the supplied options imply that we have to chase symlinks?
 272     static boolean followLinks(LinkOption... options) {
 273         if (options != null) {
 274             for (LinkOption lo : options) {
 275                 Objects.requireNonNull(lo);
 276                 if (lo == LinkOption.NOFOLLOW_LINKS) {
 277                     return false;
 278                 } else {
 279                     throw new AssertionError("should not reach here");
 280                 }
 281             }
 282         }
 283         return true;
 284     }
 285 
 286     // check that the options passed are supported by (read-only) jrt file system
 287     static void checkOptions(Set<? extends OpenOption> options) {
 288         // check for options of null type and option is an intance of StandardOpenOption
 289         for (OpenOption option : options) {
 290             Objects.requireNonNull(option);
 291             if (!(option instanceof StandardOpenOption)) {
 292                 throw new IllegalArgumentException();
 293             }
 294         }
 295         if (options.contains(StandardOpenOption.WRITE) ||
 296             options.contains(StandardOpenOption.APPEND)) {
 297             throw readOnly();
 298         }
 299     }
 300 
 301     // clean up this file system - called from finalize and close
 302     void cleanup() throws IOException {
 303         if (!isOpen) {
 304             return;
 305         }
 306         synchronized (this) {
 307             isOpen = false;
 308             // close image reader and null out
 309             image.close();
 310             image = null;
 311         }
 312     }
 313 
 314     // These methods throw read only file system exception
 315     final void setTimes(JrtPath jrtPath, FileTime mtime, FileTime atime, FileTime ctime)
 316             throws IOException {
 317         throw readOnly();
 318     }
 319 
 320     // These methods throw read only file system exception
 321     final void createDirectory(JrtPath jrtPath, FileAttribute<?>... attrs) throws IOException {
 322         throw readOnly();
 323     }
 324 
 325     final void deleteFile(JrtPath jrtPath, boolean failIfNotExists)
 326             throws IOException {
 327         throw readOnly();
 328     }
 329 
 330     final OutputStream newOutputStream(JrtPath jrtPath, OpenOption... options)
 331             throws IOException {
 332         throw readOnly();
 333     }
 334 
 335     final void copyFile(boolean deletesrc, JrtPath srcPath, JrtPath dstPath, CopyOption... options)
 336             throws IOException {
 337         throw readOnly();
 338     }
 339 
 340     final FileChannel newFileChannel(JrtPath path,
 341             Set<? extends OpenOption> options,
 342             FileAttribute<?>... attrs)
 343             throws IOException {
 344         throw new UnsupportedOperationException("newFileChannel");
 345     }
 346 
 347     final InputStream newInputStream(JrtPath path) throws IOException {
 348         return new ByteArrayInputStream(getFileContent(path));
 349     }
 350 
 351     final SeekableByteChannel newByteChannel(JrtPath path,
 352             Set<? extends OpenOption> options,
 353             FileAttribute<?>... attrs)
 354             throws IOException {
 355         checkOptions(options);
 356 
 357         byte[] buf = getFileContent(path);
 358         final ReadableByteChannel rbc
 359                 = Channels.newChannel(new ByteArrayInputStream(buf));
 360         final long size = buf.length;
 361         return new SeekableByteChannel() {
 362             long read = 0;
 363 
 364             @Override
 365             public boolean isOpen() {
 366                 return rbc.isOpen();
 367             }
 368 
 369             @Override
 370             public long position() throws IOException {
 371                 return read;
 372             }
 373 
 374             @Override
 375             public SeekableByteChannel position(long pos)
 376                     throws IOException {
 377                 throw new UnsupportedOperationException();
 378             }
 379 
 380             @Override
 381             public int read(ByteBuffer dst) throws IOException {
 382                 int n = rbc.read(dst);
 383                 if (n > 0) {
 384                     read += n;
 385                 }
 386                 return n;
 387             }
 388 
 389             @Override
 390             public SeekableByteChannel truncate(long size)
 391                     throws IOException {
 392                 throw new NonWritableChannelException();
 393             }
 394 
 395             @Override
 396             public int write(ByteBuffer src) throws IOException {
 397                 throw new NonWritableChannelException();
 398             }
 399 
 400             @Override
 401             public long size() throws IOException {
 402                 return size;
 403             }
 404 
 405             @Override
 406             public void close() throws IOException {
 407                 rbc.close();
 408             }
 409         };
 410     }
 411 
 412     final JrtFileStore getFileStore(JrtPath path) {
 413         return new JrtFileStore(path);
 414     }
 415 
 416     final void ensureOpen() throws IOException {
 417         if (!isOpen()) {
 418             throw new ClosedFileSystemException();
 419         }
 420     }
 421 
 422     final JrtPath getRootPath() {
 423         return rootPath;
 424     }
 425 
 426     boolean isSameFile(JrtPath path1, JrtPath path2) throws IOException {
 427         return checkNode(path1) == checkNode(path2);
 428     }
 429 
 430     boolean isLink(JrtPath path) throws IOException {
 431         return checkNode(path).isLink();
 432     }
 433 
 434     boolean exists(JrtPath path) throws IOException {
 435         try {
 436             checkNode(path);
 437         } catch (NoSuchFileException exp) {
 438             return false;
 439         }
 440         return true;
 441     }
 442 
 443     boolean isDirectory(JrtPath path, boolean resolveLinks)
 444             throws IOException {
 445         Node node = checkNode(path);
 446         return resolveLinks && node.isLink()
 447                 ? node.resolveLink(true).isDirectory()
 448                 : node.isDirectory();
 449     }
 450 
 451     JrtPath toRealPath(JrtPath path, LinkOption... options)
 452             throws IOException {
 453         Node node = checkNode(path);
 454         if (followLinks(options) && node.isLink()) {
 455             node = node.resolveLink();
 456         }
 457         // image node holds the real/absolute path name
 458         return new JrtPath(this, node.getName(), true);
 459     }
 460 
 461     private Node lookup(String path) {
 462         try {
 463             return image.findNode(path);
 464         } catch (RuntimeException re) {
 465             throw new InvalidPathException(path, re.toString());
 466         }
 467     }
 468 
 469     private Node lookupSymbolic(String path) {
 470         int i = 1;
 471         while (i < path.length()) {
 472             i = path.indexOf('/', i);
 473             if (i == -1) {
 474                 break;
 475             }
 476             String prefix = path.substring(0, i);
 477             Node node = lookup(prefix);
 478             if (node == null) {
 479                 break;
 480             }
 481             if (node.isLink()) {
 482                 Node link = node.resolveLink(true);
 483                 // resolved symbolic path concatenated to the rest of the path
 484                 String resPath = link.getName() + path.substring(i);
 485                 node = lookup(resPath);
 486                 return node != null ? node : lookupSymbolic(resPath);
 487             }
 488             i++;
 489         }
 490         return null;
 491     }
 492 
 493     Node checkNode(JrtPath path) throws IOException {
 494         ensureOpen();
 495         String p = path.getResolvedPath();
 496         Node node = lookup(p);
 497         if (node == null) {
 498             node = lookupSymbolic(p);
 499             if (node == null) {
 500                 throw new NoSuchFileException(p);
 501             }
 502         }
 503         return node;
 504     }
 505 }