1 /*
2 * Copyright (c) 2014, 2022, 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 }