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