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