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.*;
  33 import java.nio.charset.Charset;
  34 import java.nio.file.ClosedFileSystemException;
  35 import java.nio.file.CopyOption;
  36 import java.nio.file.LinkOption;
  37 import java.nio.file.FileStore;
  38 import java.nio.file.FileSystem;
  39 import java.nio.file.FileSystemException;
  40 import java.nio.file.FileSystemNotFoundException;
  41 import java.nio.file.Files;
  42 import java.nio.file.NoSuchFileException;
  43 import java.nio.file.NotDirectoryException;
  44 import java.nio.file.OpenOption;
  45 import java.nio.file.Path;
  46 import java.nio.file.PathMatcher;
  47 import java.nio.file.ReadOnlyFileSystemException;
  48 import java.nio.file.StandardOpenOption;
  49 import java.nio.file.WatchService;
  50 import java.nio.file.attribute.FileAttribute;
  51 import java.nio.file.attribute.FileTime;
  52 import java.nio.file.attribute.UserPrincipalLookupService;
  53 import java.nio.file.spi.FileSystemProvider;
  54 import java.util.concurrent.ConcurrentHashMap;
  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.List;
  61 import java.util.Map;
  62 import java.util.Set;
  63 import java.util.function.Function;
  64 import java.util.regex.Pattern;
  65 import static java.util.stream.Collectors.toList;
  66 import jdk.internal.jimage.ImageReader;
  67 import jdk.internal.jimage.ImageReader.Node;
  68 import jdk.internal.jimage.UTF8String;
  69 
  70 /**
  71  * A FileSystem built on System jimage files.
  72  */
  73 class JrtFileSystem extends FileSystem {
  74     private static final Charset UTF_8 = Charset.forName("UTF-8");
  75 
  76     private final JrtFileSystemProvider provider;
  77     // System image readers
  78     private ImageReader bootImage;
  79     private ImageReader extImage;
  80     private ImageReader appImage;
  81     // root path
  82     private final JrtPath rootPath;
  83     private volatile boolean isOpen;
  84 
  85     private static void checkExists(Path path) {
  86         if (Files.notExists(path)) {
  87             throw new FileSystemNotFoundException(path.toString());
  88         }
  89     }
  90 
  91     // open a .jimage and build directory structure
  92     private static ImageReader openImage(Path path) throws IOException {
  93         ImageReader image = ImageReader.open(path.toString());
  94         image.getRootDirectory();
  95         return image;
  96     }
  97 
  98     JrtFileSystem(JrtFileSystemProvider provider,
  99             Map<String, ?> env)
 100             throws IOException {
 101         this.provider = provider;
 102         checkExists(SystemImages.bootImagePath);
 103         checkExists(SystemImages.extImagePath);
 104         checkExists(SystemImages.appImagePath);
 105 
 106         // open image files
 107         this.bootImage = openImage(SystemImages.bootImagePath);
 108         this.extImage = openImage(SystemImages.extImagePath);
 109         this.appImage = openImage(SystemImages.appImagePath);
 110 
 111         byte[] root = new byte[] { '/' };
 112         rootPath = new JrtPath(this, root);
 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 reader and null out
 153             bootImage.close();
 154             bootImage = null;
 155             extImage.close();
 156             extImage = null;
 157             appImage.close();
 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.equalsIgnoreCase(GLOB_SYNTAX)) {
 257             expr = JrtUtils.toRegexPattern(input);
 258         } else {
 259             if (syntax.equalsIgnoreCase(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     static byte[] getBytes(String name) {
 272         return name.getBytes(UTF_8);
 273     }
 274 
 275     static 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 lookup(byte[] path) {
 293         Node node = bootImage.findNode(path);
 294         ImageReader image = bootImage;
 295         if (node == null) {
 296             node = extImage.findNode(path);
 297             image = extImage;
 298         }
 299         if (node == null) {
 300             node = appImage.findNode(path);
 301             image = appImage;
 302         }
 303         return node != null? new NodeAndImage(node, image) : null;
 304     }
 305 
 306     private NodeAndImage lookupSymbolic(byte[] path) {
 307         for (int i = 1; i < path.length; i++) {
 308             if (path[i] == (byte)'/') {
 309                 byte[] prefix = Arrays.copyOfRange(path, 0, i);
 310                 NodeAndImage ni = lookup(prefix);
 311                 if (ni == null) {
 312                     break;
 313                 }
 314 
 315                 if (ni.node.isLink()) {
 316                     Node link = ni.node.resolveLink(true);
 317                     // resolved symbolic path concatenated to the rest of the path
 318                     UTF8String resPath = link.getName().concat(new UTF8String(path, i));
 319                     byte[] resPathBytes = resPath.getBytesCopy();
 320                     ni = lookup(resPathBytes);
 321                     return ni != null? ni : lookupSymbolic(resPathBytes);
 322                 }
 323             }
 324         }
 325 
 326         return null;
 327     }
 328 
 329     private NodeAndImage findNode(byte[] path) throws IOException {
 330         NodeAndImage ni = lookup(path);
 331         if (ni == null) {
 332             ni = lookupSymbolic(path);
 333             if (ni == null) {
 334                 throw new NoSuchFileException(getString(path));
 335             }
 336         }
 337         return ni;
 338     }
 339 
 340     private NodeAndImage checkNode(byte[] path) throws IOException {
 341         ensureOpen();
 342         return findNode(path);
 343     }
 344 
 345     private NodeAndImage checkResource(byte[] path) throws IOException {
 346         NodeAndImage ni = checkNode(path);
 347         if (ni.node.isDirectory()) {
 348             throw new FileSystemException(getString(path) + " is a directory");
 349         }
 350 
 351         assert ni.node.isResource() : "resource node expected here";
 352         return ni;
 353     }
 354 
 355     static boolean followLinks(LinkOption... options) {
 356         if (options != null) {
 357             for (LinkOption lo : options) {
 358                 if (lo == LinkOption.NOFOLLOW_LINKS) {
 359                     return false;
 360                 } else if (lo == null) {
 361                     throw new NullPointerException();
 362                 } else {
 363                     throw new AssertionError("should not reach here");
 364                 }
 365             }
 366         }
 367         return true;
 368     }
 369 
 370     // package private helpers
 371     JrtFileAttributes getFileAttributes(byte[] path, LinkOption... options)
 372             throws IOException {
 373         NodeAndImage ni = checkNode(path);
 374         if (ni.node.isLink() && followLinks(options)) {
 375             return new JrtFileAttributes(ni.node.resolveLink(true));
 376         }
 377         return new JrtFileAttributes(ni.node);
 378     }
 379 
 380     void setTimes(byte[] path, FileTime mtime, FileTime atime, FileTime ctime)
 381             throws IOException {
 382         throw readOnly();
 383     }
 384 
 385     boolean exists(byte[] path) throws IOException {
 386         ensureOpen();
 387         try {
 388             findNode(path);
 389         } catch (NoSuchFileException exp) {
 390             return false;
 391         }
 392         return true;
 393     }
 394 
 395     boolean isDirectory(byte[] path, boolean resolveLinks)
 396             throws IOException {
 397         ensureOpen();
 398         NodeAndImage ni = checkNode(path);
 399         return resolveLinks && ni.node.isLink()?
 400             ni.node.resolveLink(true).isDirectory() :
 401             ni.node.isDirectory();
 402     }
 403 
 404     JrtPath toJrtPath(String path) {
 405         return toJrtPath(getBytes(path));
 406     }
 407 
 408     JrtPath toJrtPath(byte[] path) {
 409         return new JrtPath(this, path);
 410     }
 411 
 412     boolean isSameFile(JrtPath p1, JrtPath p2) throws IOException {
 413         NodeAndImage n1 = findNode(p1.getName());
 414         NodeAndImage n2 = findNode(p2.getName());
 415         return n1.node.equals(n2.node);
 416     }
 417 
 418     boolean isLink(JrtPath jrtPath) throws IOException {
 419         return findNode(jrtPath.getName()).node.isLink();
 420     }
 421 
 422     JrtPath resolveLink(JrtPath jrtPath) throws IOException {
 423         NodeAndImage ni = findNode(jrtPath.getName());
 424         if (ni.node.isLink()) {
 425             Node node = ni.node.resolveLink();
 426             return toJrtPath(node.getName().getBytesCopy());
 427         }
 428 
 429         return jrtPath;
 430     }
 431 
 432     private Map<UTF8String, List<Node>> packagesTreeChildren = new ConcurrentHashMap<>();
 433 
 434     /**
 435      * returns the list of child paths of the given directory "path"
 436      *
 437      * @param path name of the directory whose content is listed
 438      * @param childPrefix prefix added to returned children names - may be null
 439               in which case absolute child paths are returned
 440      * @return iterator for child paths of the given directory path
 441      */
 442     Iterator<Path> iteratorOf(byte[] path, String childPrefix)
 443             throws IOException {
 444         NodeAndImage ni = checkNode(path);
 445         Node node = ni.node.resolveLink(true);
 446 
 447         if (!node.isDirectory()) {
 448             throw new NotDirectoryException(getString(path));
 449         }
 450 
 451         if (node.isRootDir()) {
 452             return rootDirIterator(path, childPrefix);
 453         } else if (node.isModulesDir()) {
 454             return modulesDirIterator(path, childPrefix);
 455         } else if (node.isPackagesDir()) {
 456             return packagesDirIterator(path, childPrefix);
 457         } else if (node.getNameString().startsWith("/packages/")) {
 458             if (ni.image != appImage) {
 459                 UTF8String name = node.getName();
 460                 List<Node> children = packagesTreeChildren.get(name);
 461                 if (children != null) {
 462                     return nodesToIterator(toJrtPath(path), childPrefix, children);
 463                 }
 464 
 465                 children = new ArrayList<>();
 466                 children.addAll(node.getChildren());
 467                 Node tmpNode = null;
 468                 // found in boot
 469                 if (ni.image == bootImage) {
 470                     tmpNode = extImage.findNode(name);
 471                     if (tmpNode != null) {
 472                         children.addAll(tmpNode.getChildren());
 473                     }
 474                 }
 475 
 476                 // found in ext
 477                 tmpNode = appImage.findNode(name);
 478                 if (tmpNode != null) {
 479                     children.addAll(tmpNode.getChildren());
 480                 }
 481 
 482                 packagesTreeChildren.put(name, children);
 483                 return nodesToIterator(toJrtPath(path), childPrefix, children);
 484             }
 485         }
 486 
 487         return nodesToIterator(toJrtPath(path), childPrefix, node.getChildren());
 488     }
 489 
 490     private Iterator<Path> nodesToIterator(Path path, String childPrefix, List<Node> childNodes) {
 491         Function<Node, Path> f = childPrefix == null
 492                 ? child -> toJrtPath(child.getNameString())
 493                 : child -> toJrtPath(childPrefix + child.getNameString().substring(1));
 494          return childNodes.stream().map(f).collect(toList()).iterator();
 495     }
 496 
 497     private void addRootDirContent(List<Node> children) {
 498         for (Node child : children) {
 499             if (!(child.isModulesDir() || child.isPackagesDir())) {
 500                 rootChildren.add(child);
 501             }
 502         }
 503     }
 504 
 505     private List<Node> rootChildren;
 506     private synchronized void initRootChildren(byte[] path) {
 507         if (rootChildren == null) {
 508             rootChildren = new ArrayList<>();
 509             rootChildren.addAll(bootImage.findNode(path).getChildren());
 510             addRootDirContent(extImage.findNode(path).getChildren());
 511             addRootDirContent(appImage.findNode(path).getChildren());
 512         }
 513     }
 514 
 515     private Iterator<Path> rootDirIterator(byte[] path, String childPrefix) throws IOException {
 516         initRootChildren(path);
 517         return nodesToIterator(rootPath, childPrefix, rootChildren);
 518     }
 519 
 520     private List<Node> modulesChildren;
 521     private synchronized void initModulesChildren(byte[] path) {
 522         if (modulesChildren == null) {
 523             modulesChildren = new ArrayList<>();
 524             modulesChildren.addAll(bootImage.findNode(path).getChildren());
 525             modulesChildren.addAll(appImage.findNode(path).getChildren());
 526             modulesChildren.addAll(extImage.findNode(path).getChildren());
 527         }
 528     }
 529 
 530     private Iterator<Path> modulesDirIterator(byte[] path, String childPrefix) throws IOException {
 531         initModulesChildren(path);
 532         return nodesToIterator(new JrtPath(this, path), childPrefix, modulesChildren);
 533     }
 534 
 535     private List<Node> packagesChildren;
 536     private synchronized void initPackagesChildren(byte[] path) {
 537         if (packagesChildren == null) {
 538             packagesChildren = new ArrayList<>();
 539             packagesChildren.addAll(bootImage.findNode(path).getChildren());
 540             packagesChildren.addAll(extImage.findNode(path).getChildren());
 541             packagesChildren.addAll(appImage.findNode(path).getChildren());
 542         }
 543     }
 544     private Iterator<Path> packagesDirIterator(byte[] path, String childPrefix) throws IOException {
 545         initPackagesChildren(path);
 546         return nodesToIterator(new JrtPath(this, path), childPrefix, packagesChildren);
 547     }
 548 
 549     void createDirectory(byte[] dir, FileAttribute<?>... attrs)
 550             throws IOException {
 551         throw readOnly();
 552     }
 553 
 554     void copyFile(boolean deletesrc, byte[] src, byte[] dst, CopyOption... options)
 555             throws IOException {
 556         throw readOnly();
 557     }
 558 
 559     public void deleteFile(byte[] path, boolean failIfNotExists)
 560             throws IOException {
 561         throw readOnly();
 562     }
 563 
 564     OutputStream newOutputStream(byte[] path, OpenOption... options)
 565             throws IOException {
 566         throw readOnly();
 567     }
 568 
 569     private void checkOptions(Set<? extends OpenOption> options) {
 570         // check for options of null type and option is an intance of StandardOpenOption
 571         for (OpenOption option : options) {
 572             if (option == null) {
 573                 throw new NullPointerException();
 574             }
 575             if (!(option instanceof StandardOpenOption)) {
 576                 throw new IllegalArgumentException();
 577             }
 578         }
 579     }
 580 
 581     // Returns an input stream for reading the contents of the specified
 582     // file entry.
 583     InputStream newInputStream(byte[] path) throws IOException {
 584         final NodeAndImage ni = checkResource(path);
 585         return new ByteArrayInputStream(ni.getResource());
 586     }
 587 
 588     SeekableByteChannel newByteChannel(byte[] path,
 589             Set<? extends OpenOption> options,
 590             FileAttribute<?>... attrs)
 591             throws IOException {
 592         checkOptions(options);
 593         if (options.contains(StandardOpenOption.WRITE)
 594                 || options.contains(StandardOpenOption.APPEND)) {
 595             throw readOnly();
 596         }
 597 
 598         NodeAndImage ni = checkResource(path);
 599         byte[] buf = ni.getResource();
 600         final ReadableByteChannel rbc
 601                 = Channels.newChannel(new ByteArrayInputStream(buf));
 602         final long size = buf.length;
 603         return new SeekableByteChannel() {
 604             long read = 0;
 605 
 606             @Override
 607             public boolean isOpen() {
 608                 return rbc.isOpen();
 609             }
 610 
 611             @Override
 612             public long position() throws IOException {
 613                 return read;
 614             }
 615 
 616             @Override
 617             public SeekableByteChannel position(long pos)
 618                     throws IOException {
 619                 throw new UnsupportedOperationException();
 620             }
 621 
 622             @Override
 623             public int read(ByteBuffer dst) throws IOException {
 624                 int n = rbc.read(dst);
 625                 if (n > 0) {
 626                     read += n;
 627                 }
 628                 return n;
 629             }
 630 
 631             @Override
 632             public SeekableByteChannel truncate(long size)
 633                     throws IOException {
 634                 throw new NonWritableChannelException();
 635             }
 636 
 637             @Override
 638             public int write(ByteBuffer src) throws IOException {
 639                 throw new NonWritableChannelException();
 640             }
 641 
 642             @Override
 643             public long size() throws IOException {
 644                 return size;
 645             }
 646 
 647             @Override
 648             public void close() throws IOException {
 649                 rbc.close();
 650             }
 651         };
 652     }
 653 
 654     // Returns a FileChannel of the specified path.
 655     FileChannel newFileChannel(byte[] path,
 656             Set<? extends OpenOption> options,
 657             FileAttribute<?>... attrs)
 658             throws IOException {
 659         throw new UnsupportedOperationException("newFileChannel");
 660     }
 661 }