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 }