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 }