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