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