1 /*
   2  * Copyright (c) 2014, 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.*;
  33 import java.nio.charset.Charset;
  34 import java.nio.file.AccessMode;
  35 import java.nio.file.ClosedFileSystemException;
  36 import java.nio.file.CopyOption;
  37 import java.nio.file.DirectoryStream;
  38 import java.nio.file.FileStore;
  39 import java.nio.file.FileSystem;
  40 import java.nio.file.FileSystemException;
  41 import java.nio.file.FileSystemNotFoundException;
  42 import java.nio.file.Files;
  43 import java.nio.file.NoSuchFileException;
  44 import java.nio.file.NotDirectoryException;
  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.StandardCopyOption;
  50 import java.nio.file.StandardOpenOption;
  51 import java.nio.file.WatchService;
  52 import java.nio.file.attribute.FileAttribute;
  53 import java.nio.file.attribute.FileTime;
  54 import java.nio.file.attribute.UserPrincipalLookupService;
  55 import java.nio.file.spi.FileSystemProvider;
  56 import java.security.AccessController;
  57 import java.security.PrivilegedActionException;
  58 import java.security.PrivilegedExceptionAction;
  59 import java.util.ArrayList;
  60 import java.util.Arrays;
  61 import java.util.Collections;
  62 import java.util.HashSet;
  63 import java.util.Iterator;
  64 import java.util.List;
  65 import java.util.Map;
  66 import java.util.Set;
  67 import java.util.regex.Pattern;
  68 import jdk.internal.jimage.ImageReader;
  69 import jdk.internal.jimage.ImageReader.Node;
  70 import jdk.internal.jimage.UTF8String;
  71 
  72 /**
  73  * A FileSystem built on System jimage files.
  74  */
  75 class JrtFileSystem extends FileSystem {
  76     private static final Charset UTF_8 = Charset.forName("UTF-8");
  77     private final JrtFileSystemProvider provider;
  78     // System image readers
  79     private ImageReader bootImage;
  80     private ImageReader extImage;
  81     private ImageReader appImage;
  82     // root path
  83     private final JrtPath rootPath;
  84     private volatile boolean isOpen;
  85 
  86     private static void checkExists(Path path) {
  87         if (Files.notExists(path)) {
  88             throw new FileSystemNotFoundException(path.toString());
  89         }
  90     }
  91 
  92     // open a .jimage and build directory structure
  93     private static ImageReader openImage(Path path) throws IOException {
  94         ImageReader image = ImageReader.open(path.toString());
  95         image.getRootDirectory();
  96         return image;
  97     }
  98 
  99     JrtFileSystem(JrtFileSystemProvider provider,
 100             Map<String, ?> env)
 101             throws IOException {
 102         this.provider = provider;
 103         checkExists(SystemImages.bootImagePath);
 104         checkExists(SystemImages.extImagePath);
 105         checkExists(SystemImages.appImagePath);
 106 
 107         // open image files
 108         this.bootImage = openImage(SystemImages.bootImagePath);
 109         this.extImage = openImage(SystemImages.extImagePath);
 110         this.appImage = openImage(SystemImages.appImagePath);
 111 
 112         rootPath = new JrtPath(this, new byte[]{'/'});
 113         isOpen = true;
 114     }
 115 
 116     @Override
 117     public FileSystemProvider provider() {
 118         return provider;
 119     }
 120 
 121     @Override
 122     public String getSeparator() {
 123         return "/";
 124     }
 125 
 126     @Override
 127     public boolean isOpen() {
 128         return isOpen;
 129     }
 130 
 131     @Override
 132     public void close() throws IOException {
 133         cleanup();
 134     }
 135 
 136     @Override
 137     protected void finalize() {
 138         try {
 139             cleanup();
 140         } catch (IOException ignored) {}
 141     }
 142 
 143     // clean up this file system - called from finalize and close
 144     private void cleanup() throws IOException {
 145         if (!isOpen) {
 146             return;
 147         }
 148 
 149         synchronized(this) {
 150             isOpen = false;
 151 
 152             // close all image readers and null out
 153             bootImage.close();
 154             extImage.close();
 155             appImage.close();
 156             bootImage = null;
 157             extImage = null;
 158             appImage = null;
 159         }
 160     }
 161 
 162     private void ensureOpen() throws IOException {
 163         if (!isOpen) {
 164             throw new ClosedFileSystemException();
 165         }
 166     }
 167 
 168     @Override
 169     public boolean isReadOnly() {
 170         return true;
 171     }
 172 
 173     private ReadOnlyFileSystemException readOnly() {
 174         return new ReadOnlyFileSystemException();
 175     }
 176 
 177     @Override
 178     public Iterable<Path> getRootDirectories() {
 179         ArrayList<Path> pathArr = new ArrayList<>();
 180         pathArr.add(rootPath);
 181         return pathArr;
 182     }
 183 
 184     JrtPath getRootPath() {
 185         return rootPath;
 186     }
 187 
 188     @Override
 189     public JrtPath getPath(String first, String... more) {
 190         String path;
 191         if (more.length == 0) {
 192             path = first;
 193         } else {
 194             StringBuilder sb = new StringBuilder();
 195             sb.append(first);
 196             for (String segment : more) {
 197                 if (segment.length() > 0) {
 198                     if (sb.length() > 0) {
 199                         sb.append('/');
 200                     }
 201                     sb.append(segment);
 202                 }
 203             }
 204             path = sb.toString();
 205         }
 206         return new JrtPath(this, getBytes(path));
 207     }
 208 
 209     @Override
 210     public UserPrincipalLookupService getUserPrincipalLookupService() {
 211         throw new UnsupportedOperationException();
 212     }
 213 
 214     @Override
 215     public WatchService newWatchService() {
 216         throw new UnsupportedOperationException();
 217     }
 218 
 219     FileStore getFileStore(JrtPath path) {
 220         return new JrtFileStore(path);
 221     }
 222 
 223     @Override
 224     public Iterable<FileStore> getFileStores() {
 225         ArrayList<FileStore> list = new ArrayList<>(1);
 226         list.add(new JrtFileStore(new JrtPath(this, new byte[]{'/'})));
 227         return list;
 228     }
 229 
 230     private static final Set<String> supportedFileAttributeViews
 231             = Collections.unmodifiableSet(
 232                     new HashSet<String>(Arrays.asList("basic", "jrt")));
 233 
 234     @Override
 235     public Set<String> supportedFileAttributeViews() {
 236         return supportedFileAttributeViews;
 237     }
 238 
 239     @Override
 240     public String toString() {
 241         return "jrt:/";
 242     }
 243 
 244     private static final String GLOB_SYNTAX = "glob";
 245     private static final String REGEX_SYNTAX = "regex";
 246 
 247     @Override
 248     public PathMatcher getPathMatcher(String syntaxAndInput) {
 249         int pos = syntaxAndInput.indexOf(':');
 250         if (pos <= 0 || pos == syntaxAndInput.length()) {
 251             throw new IllegalArgumentException();
 252         }
 253         String syntax = syntaxAndInput.substring(0, pos);
 254         String input = syntaxAndInput.substring(pos + 1);
 255         String expr;
 256         if (syntax.equals(GLOB_SYNTAX)) {
 257             expr = JrtUtils.toRegexPattern(input);
 258         } else {
 259             if (syntax.equals(REGEX_SYNTAX)) {
 260                 expr = input;
 261             } else {
 262                 throw new UnsupportedOperationException("Syntax '" + syntax
 263                         + "' not recognized");
 264             }
 265         }
 266         // return matcher
 267         final Pattern pattern = Pattern.compile(expr);
 268         return (Path path) -> pattern.matcher(path.toString()).matches();
 269     }
 270 
 271     final byte[] getBytes(String name) {
 272         return name.getBytes(UTF_8);
 273     }
 274 
 275     final String getString(byte[] name) {
 276         return new String(name, UTF_8);
 277     }
 278 
 279     private static class NodeAndImage {
 280         final Node node;
 281         final ImageReader image;
 282 
 283         NodeAndImage(Node node, ImageReader image) {
 284             this.node = node; this.image = image;
 285         }
 286 
 287         byte[] getResource() throws IOException {
 288             return image.getResource(node);
 289         }
 290     }
 291 
 292     private NodeAndImage findNode(byte[] path) throws IOException {
 293         ImageReader image = bootImage;
 294         Node node = bootImage.findNode(path);
 295         if (node == null) {
 296             image = extImage;
 297             node = extImage.findNode(path);
 298         }
 299         if (node == null) {
 300             image = appImage;
 301             node = appImage.findNode(path);
 302         }
 303         if (node == null || node.isTopLevelPackageDir()) {
 304             throw new NoSuchFileException(getString(path));
 305         }
 306         return new NodeAndImage(node, image);
 307     }
 308 
 309     private NodeAndImage checkNode(byte[] path) throws IOException {
 310         ensureOpen();
 311         return findNode(path);
 312     }
 313 
 314     private NodeAndImage checkResource(byte[] path) throws IOException {
 315         NodeAndImage ni = checkNode(path);
 316         if (ni.node.isDirectory()) {
 317             throw new FileSystemException(getString(path) + " is a directory");
 318         }
 319 
 320         assert ni.node.isResource() : "resource node expected here";
 321         return ni;
 322     }
 323 
 324     // package private helpers
 325     JrtFileAttributes getFileAttributes(byte[] path)
 326             throws IOException {
 327         NodeAndImage ni = checkNode(path);
 328         return new JrtFileAttributes(ni.node);
 329     }
 330 
 331     void setTimes(byte[] path, FileTime mtime, FileTime atime, FileTime ctime)
 332             throws IOException {
 333         throw readOnly();
 334     }
 335 
 336     boolean exists(byte[] path) throws IOException {
 337         ensureOpen();
 338         try {
 339             findNode(path);
 340         } catch (NoSuchFileException exp) {
 341             return false;
 342         }
 343         return true;
 344     }
 345 
 346     boolean isDirectory(byte[] path)
 347             throws IOException {
 348         ensureOpen();
 349         NodeAndImage ni = checkNode(path);
 350         return ni.node.isDirectory();
 351     }
 352 
 353     JrtPath toJrtPath(String path) {
 354         return toJrtPath(getBytes(path));
 355     }
 356 
 357     JrtPath toJrtPath(byte[] path) {
 358         return new JrtPath(this, path);
 359     }
 360 
 361     // returns the list of child paths of "path"
 362     Iterator<Path> iteratorOf(byte[] path,
 363             DirectoryStream.Filter<? super Path> filter)
 364             throws IOException {
 365         NodeAndImage ni = checkNode(path);
 366         if (!ni.node.isDirectory()) {
 367             throw new NotDirectoryException(getString(path));
 368         }
 369 
 370         if (ni.node.isRootDir()) {
 371             return rootDirIterator(path);
 372         }
 373 
 374         return nodesToIterator(toJrtPath(path), ni.node.getChildren());
 375     }
 376 
 377     private Iterator<Path> nodesToIterator(Path path, List<Node> childNodes) {
 378         List<Path> childPaths = new ArrayList<>(childNodes.size());
 379         childNodes.stream().forEach((child) -> {
 380             childPaths.add(toJrtPath(child.getNameString()));
 381         });
 382         return childPaths.iterator();
 383     }
 384 
 385     private List<Node> rootChildren;
 386     private static void addRootDirContent(List<Node> dest, List<Node> src) {
 387         for (Node n : src) {
 388             // only module directories at the top level. Filter other stuff!
 389             if (n.isModuleDir()) {
 390                 dest.add(n);
 391             }
 392         }
 393     }
 394 
 395     private synchronized void initRootChildren(byte[] path) {
 396         if (rootChildren == null) {
 397             rootChildren = new ArrayList<>();
 398             addRootDirContent(rootChildren, bootImage.findNode(path).getChildren());
 399             addRootDirContent(rootChildren, extImage.findNode(path).getChildren());
 400             addRootDirContent(rootChildren, appImage.findNode(path).getChildren());
 401         }
 402     }
 403 
 404     private Iterator<Path> rootDirIterator(byte[] path) throws IOException {
 405         initRootChildren(path);
 406         return nodesToIterator(rootPath, rootChildren);
 407     }
 408 
 409     void createDirectory(byte[] dir, FileAttribute<?>... attrs)
 410             throws IOException {
 411         throw readOnly();
 412     }
 413 
 414     void copyFile(boolean deletesrc, byte[] src, byte[] dst, CopyOption... options)
 415             throws IOException {
 416         throw readOnly();
 417     }
 418 
 419     public void deleteFile(byte[] path, boolean failIfNotExists)
 420             throws IOException {
 421         throw readOnly();
 422     }
 423 
 424     OutputStream newOutputStream(byte[] path, OpenOption... options)
 425             throws IOException {
 426         throw readOnly();
 427     }
 428 
 429     private void checkOptions(Set<? extends OpenOption> options) {
 430         // check for options of null type and option is an intance of StandardOpenOption
 431         for (OpenOption option : options) {
 432             if (option == null) {
 433                 throw new NullPointerException();
 434             }
 435             if (!(option instanceof StandardOpenOption)) {
 436                 throw new IllegalArgumentException();
 437             }
 438         }
 439     }
 440 
 441     // Returns an input stream for reading the contents of the specified
 442     // file entry.
 443     InputStream newInputStream(byte[] path) throws IOException {
 444         final NodeAndImage ni = checkResource(path);
 445         return new ByteArrayInputStream(ni.getResource());
 446     }
 447 
 448     SeekableByteChannel newByteChannel(byte[] path,
 449             Set<? extends OpenOption> options,
 450             FileAttribute<?>... attrs)
 451             throws IOException {
 452         checkOptions(options);
 453         if (options.contains(StandardOpenOption.WRITE)
 454                 || options.contains(StandardOpenOption.APPEND)) {
 455             throw readOnly();
 456         }
 457 
 458         NodeAndImage ni = checkResource(path);
 459         byte[] buf = ni.getResource();
 460         final ReadableByteChannel rbc
 461                 = Channels.newChannel(new ByteArrayInputStream(buf));
 462         final long size = buf.length;
 463         return new SeekableByteChannel() {
 464             long read = 0;
 465 
 466             @Override
 467             public boolean isOpen() {
 468                 return rbc.isOpen();
 469             }
 470 
 471             @Override
 472             public long position() throws IOException {
 473                 return read;
 474             }
 475 
 476             @Override
 477             public SeekableByteChannel position(long pos)
 478                     throws IOException {
 479                 throw new UnsupportedOperationException();
 480             }
 481 
 482             @Override
 483             public int read(ByteBuffer dst) throws IOException {
 484                 int n = rbc.read(dst);
 485                 if (n > 0) {
 486                     read += n;
 487                 }
 488                 return n;
 489             }
 490 
 491             @Override
 492             public SeekableByteChannel truncate(long size)
 493                     throws IOException {
 494                 throw new NonWritableChannelException();
 495             }
 496 
 497             @Override
 498             public int write(ByteBuffer src) throws IOException {
 499                 throw new NonWritableChannelException();
 500             }
 501 
 502             @Override
 503             public long size() throws IOException {
 504                 return size;
 505             }
 506 
 507             @Override
 508             public void close() throws IOException {
 509                 rbc.close();
 510             }
 511         };
 512     }
 513 
 514     // Returns a FileChannel of the specified path.
 515     FileChannel newFileChannel(byte[] path,
 516             Set<? extends OpenOption> options,
 517             FileAttribute<?>... attrs)
 518             throws IOException {
 519         throw new UnsupportedOperationException("newFileChannel");
 520     }
 521 }