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 }