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