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.AccessMode; 35 import java.nio.file.ClosedFileSystemException; 36 import java.nio.file.CopyOption; 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.StandardCopyOption; 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.security.AccessController; 56 import java.security.PrivilegedActionException; 57 import java.security.PrivilegedExceptionAction; 58 import java.util.ArrayList; 59 import java.util.Arrays; 60 import java.util.Collections; 61 import java.util.HashSet; 62 import java.util.Iterator; 63 import java.util.List; 64 import java.util.Map; 65 import java.util.Set; 66 import java.util.regex.Pattern; 67 import java.util.stream.Collectors; 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.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 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.isHidden()) { 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 /** 362 * returns the list of child paths of the given directory "path" 363 * 364 * @param path name of the directory whose content is listed 365 * @param childPrefix prefix added to returned children names - may be null 366 in which case absolute child paths are returned 367 * @return iterator for child paths of the given directory path 368 */ 369 Iterator<Path> iteratorOf(byte[] path, String childPrefix) 370 throws IOException { 371 NodeAndImage ni = checkNode(path); 372 if (!ni.node.isDirectory()) { 373 throw new NotDirectoryException(getString(path)); 374 } 375 376 if (ni.node.isRootDir()) { 377 return rootDirIterator(path, childPrefix); 378 } 379 380 return nodesToIterator(toJrtPath(path), childPrefix, ni.node.getChildren()); 381 } 382 383 private Iterator<Path> nodesToIterator(Path path, String childPrefix, List<Node> childNodes) { 384 List<Path> childPaths; 385 if (childPrefix == null) { 386 childPaths = childNodes.stream() 387 .filter(Node::isVisible) 388 .map(child -> toJrtPath(child.getNameString())) 389 .collect(Collectors.toCollection(ArrayList::new)); 390 } else { 391 childPaths = childNodes.stream() 392 .filter(Node::isVisible) 393 .map(child -> toJrtPath(childPrefix + child.getNameString().substring(1))) 394 .collect(Collectors.toCollection(ArrayList::new)); 395 } 396 return childPaths.iterator(); 397 } 398 399 private List<Node> rootChildren; 400 private static void addRootDirContent(List<Node> dest, List<Node> src) { 401 for (Node n : src) { 402 // only module directories at the top level. Filter other stuff! 403 if (n.isModuleDir()) { 404 dest.add(n); 405 } 406 } 407 } 408 409 private synchronized void initRootChildren(byte[] path) { 410 if (rootChildren == null) { 411 rootChildren = new ArrayList<>(); 412 addRootDirContent(rootChildren, bootImage.findNode(path).getChildren()); 413 addRootDirContent(rootChildren, extImage.findNode(path).getChildren()); 414 addRootDirContent(rootChildren, appImage.findNode(path).getChildren()); 415 } 416 } 417 418 private Iterator<Path> rootDirIterator(byte[] path, String childPrefix) throws IOException { 419 initRootChildren(path); 420 return nodesToIterator(rootPath, childPrefix, rootChildren); 421 } 422 423 void createDirectory(byte[] dir, FileAttribute<?>... attrs) 424 throws IOException { 425 throw readOnly(); 426 } 427 428 void copyFile(boolean deletesrc, byte[] src, byte[] dst, CopyOption... options) 429 throws IOException { 430 throw readOnly(); 431 } 432 433 public void deleteFile(byte[] path, boolean failIfNotExists) 434 throws IOException { 435 throw readOnly(); 436 } 437 438 OutputStream newOutputStream(byte[] path, OpenOption... options) 439 throws IOException { 440 throw readOnly(); 441 } 442 443 private void checkOptions(Set<? extends OpenOption> options) { 444 // check for options of null type and option is an intance of StandardOpenOption 445 for (OpenOption option : options) { 446 if (option == null) { 447 throw new NullPointerException(); 448 } 449 if (!(option instanceof StandardOpenOption)) { 450 throw new IllegalArgumentException(); 451 } 452 } 453 } 454 455 // Returns an input stream for reading the contents of the specified 456 // file entry. 457 InputStream newInputStream(byte[] path) throws IOException { 458 final NodeAndImage ni = checkResource(path); 459 return new ByteArrayInputStream(ni.getResource()); 460 } 461 462 SeekableByteChannel newByteChannel(byte[] path, 463 Set<? extends OpenOption> options, 464 FileAttribute<?>... attrs) 465 throws IOException { 466 checkOptions(options); 467 if (options.contains(StandardOpenOption.WRITE) 468 || options.contains(StandardOpenOption.APPEND)) { 469 throw readOnly(); 470 } 471 472 NodeAndImage ni = checkResource(path); 473 byte[] buf = ni.getResource(); 474 final ReadableByteChannel rbc 475 = Channels.newChannel(new ByteArrayInputStream(buf)); 476 final long size = buf.length; 477 return new SeekableByteChannel() { 478 long read = 0; 479 480 @Override 481 public boolean isOpen() { 482 return rbc.isOpen(); 483 } 484 485 @Override 486 public long position() throws IOException { 487 return read; 488 } 489 490 @Override 491 public SeekableByteChannel position(long pos) 492 throws IOException { 493 throw new UnsupportedOperationException(); 494 } 495 496 @Override 497 public int read(ByteBuffer dst) throws IOException { 498 int n = rbc.read(dst); 499 if (n > 0) { 500 read += n; 501 } 502 return n; 503 } 504 505 @Override 506 public SeekableByteChannel truncate(long size) 507 throws IOException { 508 throw new NonWritableChannelException(); 509 } 510 511 @Override 512 public int write(ByteBuffer src) throws IOException { 513 throw new NonWritableChannelException(); 514 } 515 516 @Override 517 public long size() throws IOException { 518 return size; 519 } 520 521 @Override 522 public void close() throws IOException { 523 rbc.close(); 524 } 525 }; 526 } 527 528 // Returns a FileChannel of the specified path. 529 FileChannel newFileChannel(byte[] path, 530 Set<? extends OpenOption> options, 531 FileAttribute<?>... attrs) 532 throws IOException { 533 throw new UnsupportedOperationException("newFileChannel"); 534 } 535 }