1/* 2 * Copyright (c) 2014, 2017, 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 */ 25package jdk.internal.jrtfs; 26 27import java.io.ByteArrayInputStream; 28import java.io.IOException; 29import java.io.InputStream; 30import java.io.OutputStream; 31import java.nio.ByteBuffer; 32import java.nio.channels.Channels; 33import java.nio.channels.FileChannel; 34import java.nio.channels.NonWritableChannelException; 35import java.nio.channels.ReadableByteChannel; 36import java.nio.channels.SeekableByteChannel; 37import java.nio.file.ClosedFileSystemException; 38import java.nio.file.CopyOption; 39import java.nio.file.DirectoryStream; 40import java.nio.file.FileStore; 41import java.nio.file.FileSystem; 42import java.nio.file.FileSystemException; 43import java.nio.file.InvalidPathException; 44import java.nio.file.LinkOption; 45import java.nio.file.NoSuchFileException; 46import java.nio.file.NotDirectoryException; 47import java.nio.file.OpenOption; 48import java.nio.file.Path; 49import java.nio.file.PathMatcher; 50import java.nio.file.ReadOnlyFileSystemException; 51import java.nio.file.StandardOpenOption; 52import java.nio.file.WatchService; 53import java.nio.file.attribute.FileAttribute; 54import java.nio.file.attribute.FileTime; 55import java.nio.file.attribute.UserPrincipalLookupService; 56import java.nio.file.spi.FileSystemProvider; 57import java.util.ArrayList; 58import java.util.Arrays; 59import java.util.Collections; 60import java.util.HashSet; 61import java.util.Iterator; 62import java.util.Map; 63import java.util.Objects; 64import java.util.Set; 65import java.util.regex.Pattern; 66import jdk.internal.jimage.ImageReader.Node; 67import static java.util.stream.Collectors.toList; 68 69/** 70 * jrt file system implementation built on System jimage files. 71 * 72 * @implNote This class needs to maintain JDK 8 source compatibility. 73 * 74 * It is used internally in the JDK to implement jimage/jrtfs access, 75 * but also compiled and delivered as part of the jrtfs.jar to support access 76 * to the jimage file provided by the shipped JDK by tools running on JDK 8. 77 */ 78class JrtFileSystem extends FileSystem { 79 80 private final JrtFileSystemProvider provider; 81 private final JrtPath rootPath = new JrtPath(this, "/"); 82 private volatile boolean isOpen; 83 private volatile boolean isClosable; 84 private SystemImage image; 85 86 JrtFileSystem(JrtFileSystemProvider provider, Map<String, ?> env) 87 throws IOException 88 { 89 this.provider = provider; 90 this.image = SystemImage.open(); // open image file 91 this.isOpen = true; 92 this.isClosable = env != null; 93 } 94 95 // FileSystem method implementations 96 @Override 97 public boolean isOpen() { 98 return isOpen; 99 } 100 101 @Override 102 public void close() throws IOException { 103 if (!isClosable) 104 throw new UnsupportedOperationException(); 105 cleanup(); 106 } 107 108 @Override 109 @SuppressWarnings("deprecation") 110 protected void finalize() throws Throwable { 111 try { 112 cleanup(); 113 } catch (IOException ignored) {} 114 } 115 116 @Override 117 public FileSystemProvider provider() { 118 return provider; 119 } 120 121 @Override 122 public Iterable<Path> getRootDirectories() { 123 return Collections.singleton(getRootPath()); 124 } 125 126 @Override 127 public JrtPath getPath(String first, String... more) { 128 if (more.length == 0) { 129 return new JrtPath(this, first); 130 } 131 StringBuilder sb = new StringBuilder(); 132 sb.append(first); 133 for (String path : more) { 134 if (path.length() > 0) { 135 if (sb.length() > 0) { 136 sb.append('/'); 137 } 138 sb.append(path); 139 } 140 } 141 return new JrtPath(this, sb.toString()); 142 } 143 144 @Override 145 public final boolean isReadOnly() { 146 return true; 147 } 148 149 @Override 150 public final UserPrincipalLookupService getUserPrincipalLookupService() { 151 throw new UnsupportedOperationException(); 152 } 153 154 @Override 155 public final WatchService newWatchService() { 156 throw new UnsupportedOperationException(); 157 } 158 159 @Override 160 public final Iterable<FileStore> getFileStores() { 161 return Collections.singleton(getFileStore(getRootPath())); 162 } 163 164 private static final Set<String> supportedFileAttributeViews 165 = Collections.unmodifiableSet( 166 new HashSet<String>(Arrays.asList("basic", "jrt"))); 167 168 @Override 169 public final Set<String> supportedFileAttributeViews() { 170 return supportedFileAttributeViews; 171 } 172 173 @Override 174 public final String toString() { 175 return "jrt:/"; 176 } 177 178 @Override 179 public final String getSeparator() { 180 return "/"; 181 } 182 183 @Override 184 public PathMatcher getPathMatcher(String syntaxAndInput) { 185 int pos = syntaxAndInput.indexOf(':'); 186 if (pos <= 0 || pos == syntaxAndInput.length()) { 187 throw new IllegalArgumentException("pos is " + pos); 188 } 189 String syntax = syntaxAndInput.substring(0, pos); 190 String input = syntaxAndInput.substring(pos + 1); 191 String expr; 192 if (syntax.equalsIgnoreCase("glob")) { 193 expr = JrtUtils.toRegexPattern(input); 194 } else if (syntax.equalsIgnoreCase("regex")) { 195 expr = input; 196 } else { 197 throw new UnsupportedOperationException("Syntax '" + syntax 198 + "' not recognized"); 199 } 200 // return matcher 201 final Pattern pattern = Pattern.compile(expr); 202 return (Path path) -> pattern.matcher(path.toString()).matches(); 203 } 204 205 JrtPath resolveLink(JrtPath path) throws IOException { 206 Node node = checkNode(path); 207 if (node.isLink()) { 208 node = node.resolveLink(); 209 return new JrtPath(this, node.getName()); // TBD, normalized? 210 } 211 return path; 212 } 213 214 JrtFileAttributes getFileAttributes(JrtPath path, LinkOption... options) 215 throws IOException { 216 Node node = checkNode(path); 217 if (node.isLink() && followLinks(options)) { 218 return new JrtFileAttributes(node.resolveLink(true)); 219 } 220 return new JrtFileAttributes(node); 221 } 222 223 /** 224 * returns the list of child paths of the given directory "path" 225 * 226 * @param path name of the directory whose content is listed 227 * @return iterator for child paths of the given directory path 228 */ 229 Iterator<Path> iteratorOf(JrtPath path, DirectoryStream.Filter<? super Path> filter) 230 throws IOException { 231 Node node = checkNode(path).resolveLink(true); 232 if (!node.isDirectory()) { 233 throw new NotDirectoryException(path.getName()); 234 } 235 if (filter == null) { 236 return node.getChildren() 237 .stream() 238 .map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName()))) 239 .iterator(); 240 } 241 return node.getChildren() 242 .stream() 243 .map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName()))) 244 .filter(p -> { try { return filter.accept(p); 245 } catch (IOException x) {} 246 return false; 247 }) 248 .iterator(); 249 } 250 251 // returns the content of the file resource specified by the path 252 byte[] getFileContent(JrtPath path) throws IOException { 253 Node node = checkNode(path); 254 if (node.isDirectory()) { 255 throw new FileSystemException(path + " is a directory"); 256 } 257 //assert node.isResource() : "resource node expected here"; 258 return image.getResource(node); 259 } 260 261 /////////////// Implementation details below this point ////////// 262 263 // static utility methods 264 static ReadOnlyFileSystemException readOnly() { 265 return new ReadOnlyFileSystemException(); 266 } 267 268 // do the supplied options imply that we have to chase symlinks? 269 static boolean followLinks(LinkOption... options) { 270 if (options != null) { 271 for (LinkOption lo : options) { 272 Objects.requireNonNull(lo); 273 if (lo == LinkOption.NOFOLLOW_LINKS) { 274 return false; 275 } else { 276 throw new AssertionError("should not reach here"); 277 } 278 } 279 } 280 return true; 281 } 282 283 // check that the options passed are supported by (read-only) jrt file system 284 static void checkOptions(Set<? extends OpenOption> options) { 285 // check for options of null type and option is an intance of StandardOpenOption 286 for (OpenOption option : options) { 287 Objects.requireNonNull(option); 288 if (!(option instanceof StandardOpenOption)) { 289 throw new IllegalArgumentException( 290 "option class: " + option.getClass()); 291 } 292 } 293 if (options.contains(StandardOpenOption.WRITE) || 294 options.contains(StandardOpenOption.APPEND)) { 295 throw readOnly(); 296 } 297 } 298 299 // clean up this file system - called from finalize and close 300 synchronized void cleanup() throws IOException { 301 if (isOpen) { 302 isOpen = false; 303 image.close(); 304 image = null; 305 } 306 } 307 308 // These methods throw read only file system exception 309 final void setTimes(JrtPath jrtPath, FileTime mtime, FileTime atime, FileTime ctime) 310 throws IOException { 311 throw readOnly(); 312 } 313 314 // These methods throw read only file system exception 315 final void createDirectory(JrtPath jrtPath, FileAttribute<?>... attrs) throws IOException { 316 throw readOnly(); 317 } 318 319 final void deleteFile(JrtPath jrtPath, boolean failIfNotExists) 320 throws IOException { 321 throw readOnly(); 322 } 323 324 final OutputStream newOutputStream(JrtPath jrtPath, OpenOption... options) 325 throws IOException { 326 throw readOnly(); 327 } 328 329 final void copyFile(boolean deletesrc, JrtPath srcPath, JrtPath dstPath, CopyOption... options) 330 throws IOException { 331 throw readOnly(); 332 } 333 334 final FileChannel newFileChannel(JrtPath path, 335 Set<? extends OpenOption> options, 336 FileAttribute<?>... attrs) 337 throws IOException { 338 throw new UnsupportedOperationException("newFileChannel"); 339 } 340 341 final InputStream newInputStream(JrtPath path) throws IOException { 342 return new ByteArrayInputStream(getFileContent(path)); 343 } 344 345 final SeekableByteChannel newByteChannel(JrtPath path, 346 Set<? extends OpenOption> options, 347 FileAttribute<?>... attrs) 348 throws IOException { 349 checkOptions(options); 350 351 byte[] buf = getFileContent(path); 352 final ReadableByteChannel rbc 353 = Channels.newChannel(new ByteArrayInputStream(buf)); 354 final long size = buf.length; 355 return new SeekableByteChannel() { 356 long read = 0; 357 358 @Override 359 public boolean isOpen() { 360 return rbc.isOpen(); 361 } 362 363 @Override 364 public long position() throws IOException { 365 return read; 366 } 367 368 @Override 369 public SeekableByteChannel position(long pos) 370 throws IOException { 371 throw new UnsupportedOperationException(); 372 } 373 374 @Override 375 public int read(ByteBuffer dst) throws IOException { 376 int n = rbc.read(dst); 377 if (n > 0) { 378 read += n; 379 } 380 return n; 381 } 382 383 @Override 384 public SeekableByteChannel truncate(long size) 385 throws IOException { 386 throw new NonWritableChannelException(); 387 } 388 389 @Override 390 public int write(ByteBuffer src) throws IOException { 391 throw new NonWritableChannelException(); 392 } 393 394 @Override 395 public long size() throws IOException { 396 return size; 397 } 398 399 @Override 400 public void close() throws IOException { 401 rbc.close(); 402 } 403 }; 404 } 405 406 final JrtFileStore getFileStore(JrtPath path) { 407 return new JrtFileStore(path); 408 } 409 410 final void ensureOpen() throws IOException { 411 if (!isOpen()) { 412 throw new ClosedFileSystemException(); 413 } 414 } 415 416 final JrtPath getRootPath() { 417 return rootPath; 418 } 419 420 boolean isSameFile(JrtPath path1, JrtPath path2) throws IOException { 421 return checkNode(path1) == checkNode(path2); 422 } 423 424 boolean isLink(JrtPath path) throws IOException { 425 return checkNode(path).isLink(); 426 } 427 428 boolean exists(JrtPath path) throws IOException { 429 try { 430 checkNode(path); 431 } catch (NoSuchFileException exp) { 432 return false; 433 } 434 return true; 435 } 436 437 boolean isDirectory(JrtPath path, boolean resolveLinks) 438 throws IOException { 439 Node node = checkNode(path); 440 return resolveLinks && node.isLink() 441 ? node.resolveLink(true).isDirectory() 442 : node.isDirectory(); 443 } 444 445 JrtPath toRealPath(JrtPath path, LinkOption... options) 446 throws IOException { 447 Node node = checkNode(path); 448 if (followLinks(options) && node.isLink()) { 449 node = node.resolveLink(); 450 } 451 // image node holds the real/absolute path name 452 return new JrtPath(this, node.getName(), true); 453 } 454 455 private Node lookup(String path) { 456 try { 457 return image.findNode(path); 458 } catch (RuntimeException | IOException ex) { 459 throw new InvalidPathException(path, ex.toString()); 460 } 461 } 462 463 private Node lookupSymbolic(String path) { 464 int i = 1; 465 while (i < path.length()) { 466 i = path.indexOf('/', i); 467 if (i == -1) { 468 break; 469 } 470 String prefix = path.substring(0, i); 471 Node node = lookup(prefix); 472 if (node == null) { 473 break; 474 } 475 if (node.isLink()) { 476 Node link = node.resolveLink(true); 477 // resolved symbolic path concatenated to the rest of the path 478 String resPath = link.getName() + path.substring(i); 479 node = lookup(resPath); 480 return node != null ? node : lookupSymbolic(resPath); 481 } 482 i++; 483 } 484 return null; 485 } 486 487 Node checkNode(JrtPath path) throws IOException { 488 ensureOpen(); 489 String p = path.getResolvedPath(); 490 Node node = lookup(p); 491 if (node == null) { 492 node = lookupSymbolic(p); 493 if (node == null) { 494 throw new NoSuchFileException(p); 495 } 496 } 497 return node; 498 } 499} 500