1 /*
   2  * Copyright (c) 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.charset.Charset;
  38 import java.nio.file.ClosedFileSystemException;
  39 import java.nio.file.CopyOption;
  40 import java.nio.file.FileStore;
  41 import java.nio.file.FileSystem;
  42 import java.nio.file.FileSystemNotFoundException;
  43 import java.nio.file.Files;
  44 import java.nio.file.LinkOption;
  45 import java.nio.file.OpenOption;
  46 import java.nio.file.Path;
  47 import java.nio.file.PathMatcher;
  48 import java.nio.file.ReadOnlyFileSystemException;
  49 import java.nio.file.StandardOpenOption;
  50 import java.nio.file.WatchService;
  51 import java.nio.file.attribute.FileAttribute;
  52 import java.nio.file.attribute.FileTime;
  53 import java.nio.file.attribute.UserPrincipalLookupService;
  54 import java.nio.file.spi.FileSystemProvider;
  55 import java.util.ArrayList;
  56 import java.util.Arrays;
  57 import java.util.Collections;
  58 import java.util.HashSet;
  59 import java.util.Iterator;
  60 import java.util.Map;
  61 import java.util.Set;
  62 import java.util.regex.Pattern;
  63 
  64 /**
  65  * Base class for jrt file systems. jrt filesystem implementations are currently
  66  * available on top of .jimage file and on top "exploded" build directories.
  67  */
  68 abstract class AbstractJrtFileSystem extends FileSystem {
  69 
  70     private final JrtFileSystemProvider provider;
  71 
  72     AbstractJrtFileSystem(JrtFileSystemProvider provider, Map<String, ?> options) {
  73         this.provider = provider;
  74     }
  75 
  76     private static final Charset UTF_8 = Charset.forName("UTF-8");
  77 
  78     // static utility methods
  79     static ReadOnlyFileSystemException readOnly() {
  80         return new ReadOnlyFileSystemException();
  81     }
  82 
  83     // if a Path does not exist, throw exception
  84     static void checkExists(Path path) {
  85         if (Files.notExists(path)) {
  86             throw new FileSystemNotFoundException(path.toString());
  87         }
  88     }
  89 
  90     static byte[] getBytes(String name) {
  91         return name.getBytes(UTF_8);
  92     }
  93 
  94     static String getString(byte[] name) {
  95         return new String(name, UTF_8);
  96     }
  97 
  98     // do the supplied options imply that we have to chase symlinks?
  99     static boolean followLinks(LinkOption... options) {
 100         if (options != null) {
 101             for (LinkOption lo : options) {
 102                 if (lo == LinkOption.NOFOLLOW_LINKS) {
 103                     return false;
 104                 } else if (lo == null) {
 105                     throw new NullPointerException();
 106                 } else {
 107                     throw new AssertionError("should not reach here");
 108                 }
 109             }
 110         }
 111         return true;
 112     }
 113 
 114     // check that the options passed are supported by (read-only) jrt file system
 115     static void checkOptions(Set<? extends OpenOption> options) {
 116         // check for options of null type and option is an intance of StandardOpenOption
 117         for (OpenOption option : options) {
 118             if (option == null) {
 119                 throw new NullPointerException();
 120             }
 121             if (!(option instanceof StandardOpenOption)) {
 122                 throw new IllegalArgumentException();
 123             }
 124         }
 125 
 126         if (options.contains(StandardOpenOption.WRITE)
 127                 || options.contains(StandardOpenOption.APPEND)) {
 128             throw readOnly();
 129         }
 130     }
 131 
 132     // FileSystem method implementations
 133     @Override
 134     public FileSystemProvider provider() {
 135         return provider;
 136     }
 137 
 138     @Override
 139     public Iterable<Path> getRootDirectories() {
 140         ArrayList<Path> pathArr = new ArrayList<>();
 141         pathArr.add(getRootPath());
 142         return pathArr;
 143     }
 144 
 145     @Override
 146     public AbstractJrtPath getPath(String first, String... more) {
 147         String path;
 148         if (more.length == 0) {
 149             path = first;
 150         } else {
 151             StringBuilder sb = new StringBuilder();
 152             sb.append(first);
 153             for (String segment : more) {
 154                 if (segment.length() > 0) {
 155                     if (sb.length() > 0) {
 156                         sb.append('/');
 157                     }
 158                     sb.append(segment);
 159                 }
 160             }
 161             path = sb.toString();
 162         }
 163         return getRootPath().newJrtPath(getBytes(path));
 164     }
 165 
 166     @Override
 167     public final boolean isReadOnly() {
 168         return true;
 169     }
 170 
 171     @Override
 172     public final UserPrincipalLookupService getUserPrincipalLookupService() {
 173         throw new UnsupportedOperationException();
 174     }
 175 
 176     @Override
 177     public final WatchService newWatchService() {
 178         throw new UnsupportedOperationException();
 179     }
 180 
 181     @Override
 182     public final Iterable<FileStore> getFileStores() {
 183         ArrayList<FileStore> list = new ArrayList<>(1);
 184         list.add(getFileStore(getRootPath()));
 185         return list;
 186     }
 187 
 188     private static final Set<String> supportedFileAttributeViews
 189             = Collections.unmodifiableSet(
 190                     new HashSet<String>(Arrays.asList("basic", "jrt")));
 191 
 192     @Override
 193     public final Set<String> supportedFileAttributeViews() {
 194         return supportedFileAttributeViews;
 195     }
 196 
 197     @Override
 198     public final String toString() {
 199         return "jrt:/";
 200     }
 201 
 202     @Override
 203     public final String getSeparator() {
 204         return "/";
 205     }
 206 
 207     private static final String GLOB_SYNTAX = "glob";
 208     private static final String REGEX_SYNTAX = "regex";
 209 
 210     @Override
 211     public PathMatcher getPathMatcher(String syntaxAndInput) {
 212         int pos = syntaxAndInput.indexOf(':');
 213         if (pos <= 0 || pos == syntaxAndInput.length()) {
 214             throw new IllegalArgumentException();
 215         }
 216         String syntax = syntaxAndInput.substring(0, pos);
 217         String input = syntaxAndInput.substring(pos + 1);
 218         String expr;
 219         if (syntax.equalsIgnoreCase(GLOB_SYNTAX)) {
 220             expr = JrtUtils.toRegexPattern(input);
 221         } else {
 222             if (syntax.equalsIgnoreCase(REGEX_SYNTAX)) {
 223                 expr = input;
 224             } else {
 225                 throw new UnsupportedOperationException("Syntax '" + syntax
 226                         + "' not recognized");
 227             }
 228         }
 229         // return matcher
 230         final Pattern pattern = Pattern.compile(expr);
 231         return (Path path) -> pattern.matcher(path.toString()).matches();
 232     }
 233 
 234     // These methods throw read only file system exception
 235     final void setTimes(AbstractJrtPath jrtPath, FileTime mtime, FileTime atime, FileTime ctime)
 236             throws IOException {
 237         throw readOnly();
 238     }
 239 
 240     final void createDirectory(AbstractJrtPath jrtPath, FileAttribute<?>... attrs) throws IOException {
 241         throw readOnly();
 242     }
 243 
 244     final void deleteFile(AbstractJrtPath jrtPath, boolean failIfNotExists)
 245             throws IOException {
 246         throw readOnly();
 247     }
 248 
 249     final OutputStream newOutputStream(AbstractJrtPath jrtPath, OpenOption... options)
 250             throws IOException {
 251         throw readOnly();
 252     }
 253 
 254     final void copyFile(boolean deletesrc, AbstractJrtPath srcPath, AbstractJrtPath dstPath, CopyOption... options)
 255             throws IOException {
 256         throw readOnly();
 257     }
 258 
 259     final FileChannel newFileChannel(AbstractJrtPath jrtPath,
 260             Set<? extends OpenOption> options,
 261             FileAttribute<?>... attrs)
 262             throws IOException {
 263         throw new UnsupportedOperationException("newFileChannel");
 264     }
 265 
 266     final InputStream newInputStream(AbstractJrtPath jrtPath) throws IOException {
 267         return new ByteArrayInputStream(getFileContent(jrtPath));
 268     }
 269 
 270     final SeekableByteChannel newByteChannel(AbstractJrtPath jrtPath,
 271             Set<? extends OpenOption> options,
 272             FileAttribute<?>... attrs)
 273             throws IOException {
 274         checkOptions(options);
 275 
 276         byte[] buf = getFileContent(jrtPath);
 277         final ReadableByteChannel rbc
 278                 = Channels.newChannel(new ByteArrayInputStream(buf));
 279         final long size = buf.length;
 280         return new SeekableByteChannel() {
 281             long read = 0;
 282 
 283             @Override
 284             public boolean isOpen() {
 285                 return rbc.isOpen();
 286             }
 287 
 288             @Override
 289             public long position() throws IOException {
 290                 return read;
 291             }
 292 
 293             @Override
 294             public SeekableByteChannel position(long pos)
 295                     throws IOException {
 296                 throw new UnsupportedOperationException();
 297             }
 298 
 299             @Override
 300             public int read(ByteBuffer dst) throws IOException {
 301                 int n = rbc.read(dst);
 302                 if (n > 0) {
 303                     read += n;
 304                 }
 305                 return n;
 306             }
 307 
 308             @Override
 309             public SeekableByteChannel truncate(long size)
 310                     throws IOException {
 311                 throw new NonWritableChannelException();
 312             }
 313 
 314             @Override
 315             public int write(ByteBuffer src) throws IOException {
 316                 throw new NonWritableChannelException();
 317             }
 318 
 319             @Override
 320             public long size() throws IOException {
 321                 return size;
 322             }
 323 
 324             @Override
 325             public void close() throws IOException {
 326                 rbc.close();
 327             }
 328         };
 329     }
 330 
 331     final JrtFileStore getFileStore(AbstractJrtPath jrtPath) {
 332         return new JrtFileStore(jrtPath);
 333     }
 334 
 335     final void ensureOpen() throws IOException {
 336         if (!isOpen()) {
 337             throw new ClosedFileSystemException();
 338         }
 339     }
 340 
 341     // abstract methods to be implemented by a particular jrt file system
 342     abstract AbstractJrtPath getRootPath();
 343 
 344     abstract boolean isSameFile(AbstractJrtPath jrtPath1, AbstractJrtPath jrtPath2) throws IOException;
 345 
 346     abstract boolean isLink(AbstractJrtPath jrtPath) throws IOException;
 347 
 348     abstract AbstractJrtPath resolveLink(AbstractJrtPath jrtPath) throws IOException;
 349 
 350     abstract AbstractJrtFileAttributes getFileAttributes(AbstractJrtPath jrtPath, LinkOption... options) throws IOException;
 351 
 352     abstract boolean exists(AbstractJrtPath jrtPath) throws IOException;
 353 
 354     abstract boolean isDirectory(AbstractJrtPath jrtPath, boolean resolveLinks) throws IOException;
 355 
 356     /**
 357      * returns the list of child paths of the given directory "path"
 358      *
 359      * @param path name of the directory whose content is listed
 360      * @return iterator for child paths of the given directory path
 361      */
 362     abstract Iterator<Path> iteratorOf(AbstractJrtPath jrtPath) throws IOException;
 363 
 364     // returns the content of the file resource specified by the path
 365     abstract byte[] getFileContent(AbstractJrtPath jrtPath) throws IOException;
 366 }