1 /*
  2  * Copyright (c) 2016, 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 jdk.internal.jimage.PreviewMode;
 28 
 29 import java.io.*;
 30 import java.net.MalformedURLException;
 31 import java.net.URL;
 32 import java.net.URLClassLoader;
 33 import java.nio.channels.*;
 34 import java.nio.file.*;
 35 import java.nio.file.DirectoryStream.Filter;
 36 import java.nio.file.attribute.*;
 37 import java.nio.file.spi.FileSystemProvider;
 38 import java.net.URI;
 39 import java.security.AccessController;
 40 import java.security.PrivilegedAction;
 41 import java.util.HashMap;
 42 import java.util.Map;
 43 import java.util.Objects;
 44 import java.util.Set;
 45 import java.util.concurrent.ExecutorService;
 46 
 47 /**
 48  * File system provider for jrt file systems. Conditionally creates jrt fs on
 49  * a jimage file, or exploded modules directory of underlying JDK.
 50  *
 51  * @implNote This class needs to maintain JDK 8 source compatibility.
 52  *
 53  * It is used internally in the JDK to implement jimage/jrtfs access,
 54  * but also compiled and delivered as part of the jrtfs.jar to support access
 55  * to the jimage file provided by the shipped JDK by tools running on JDK 8.
 56  */
 57 public final class JrtFileSystemProvider extends FileSystemProvider {
 58 
 59     private volatile FileSystem theFileSystem;
 60 
 61     public JrtFileSystemProvider() {
 62     }
 63 
 64     @Override
 65     public String getScheme() {
 66         return "jrt";
 67     }
 68 
 69     /**
 70      * Need RuntimePermission "accessSystemModules" to create or get jrt:/
 71      */
 72     @SuppressWarnings("removal")
 73     private void checkPermission() {
 74         @SuppressWarnings({ "removal", "suppression" })
 75         SecurityManager sm = System.getSecurityManager();
 76         if (sm != null) {
 77             RuntimePermission perm = new RuntimePermission("accessSystemModules");
 78             sm.checkPermission(perm);
 79         }
 80     }
 81 
 82     private void checkUri(URI uri) {
 83         if (!uri.getScheme().equalsIgnoreCase(getScheme())) {
 84             throw new IllegalArgumentException("URI does not match this provider");
 85         }
 86         if (uri.getAuthority() != null) {
 87             throw new IllegalArgumentException("Authority component present");
 88         }
 89         if (uri.getPath() == null) {
 90             throw new IllegalArgumentException("Path component is undefined");
 91         }
 92         if (!uri.getPath().equals("/")) {
 93             throw new IllegalArgumentException("Path component should be '/'");
 94         }
 95         if (uri.getQuery() != null) {
 96             throw new IllegalArgumentException("Query component present");
 97         }
 98         if (uri.getFragment() != null) {
 99             throw new IllegalArgumentException("Fragment component present");
100         }
101     }
102 
103     @Override
104     public FileSystem newFileSystem(URI uri, Map<String, ?> env)
105             throws IOException {
106         Objects.requireNonNull(env);
107         checkPermission();
108         checkUri(uri);
109         if (env.containsKey("java.home")) {
110             return newFileSystem((String)env.get("java.home"), uri, env);
111         } else {
112             return new JrtFileSystem(this, parsePreviewMode(env.get("previewMode")));
113         }
114     }
115 
116     // Currently this does not support specifying "for runtime", because it is
117     // expected that callers creating non-standard image readers will not be
118     // using them to read resources for the current runtime (they would just
119     // use "jrt:" URLs if they were doing that).
120     private static PreviewMode parsePreviewMode(Object envValue) {
121         if (envValue instanceof String && Boolean.parseBoolean((String) envValue)) {
122             return PreviewMode.ENABLED;
123         }
124         // Default (unspecified/null or bad parameter) is to not use preview mode.
125         return PreviewMode.DISABLED;
126     }
127 
128     private static final String JRT_FS_JAR = "jrt-fs.jar";
129     private FileSystem newFileSystem(String targetHome, URI uri, Map<String, ?> env)
130             throws IOException {
131         Objects.requireNonNull(targetHome);
132         Path jrtfs = FileSystems.getDefault().getPath(targetHome, "lib", JRT_FS_JAR);
133         if (Files.notExists(jrtfs)) {
134             throw new IOException(jrtfs.toString() + " not exist");
135         }
136         Map<String,?> newEnv = new HashMap<>(env);
137         newEnv.remove("java.home");
138         ClassLoader cl = newJrtFsLoader(jrtfs);
139         try {
140             Class<?> c = Class.forName(JrtFileSystemProvider.class.getName(), false, cl);
141             @SuppressWarnings({ "deprecation", "suppression" })
142             Object tmp = c.newInstance();
143             return ((FileSystemProvider)tmp).newFileSystem(uri, newEnv);
144         } catch (ClassNotFoundException |
145                  IllegalAccessException |
146                  InstantiationException e) {
147             throw new IOException(e);
148         }
149     }
150 
151     private static class JrtFsLoader extends URLClassLoader {
152         JrtFsLoader(URL[] urls) {
153             super(urls);
154         }
155         @Override
156         protected Class<?> loadClass(String cn, boolean resolve)
157                 throws ClassNotFoundException
158         {
159             Class<?> c = findLoadedClass(cn);
160             if (c == null) {
161                 URL u = findResource(cn.replace('.', '/') + ".class");
162                 if (u != null) {
163                     c = findClass(cn);
164                 } else {
165                     return super.loadClass(cn, resolve);
166                 }
167             }
168             if (resolve)
169                 resolveClass(c);
170             return c;
171         }
172     }
173 
174     @SuppressWarnings({ "removal", "suppression" })
175     private static URLClassLoader newJrtFsLoader(Path jrtfs) {
176         final URL url;
177         try {
178             url = jrtfs.toUri().toURL();
179         } catch (MalformedURLException mue) {
180             throw new IllegalArgumentException(mue);
181         }
182 
183         final URL[] urls = new URL[] { url };
184         return AccessController.doPrivileged(
185                 new PrivilegedAction<URLClassLoader>() {
186                     @Override
187                     public URLClassLoader run() {
188                         return new JrtFsLoader(urls);
189                     }
190                 }
191         );
192     }
193 
194     @Override
195     public Path getPath(URI uri) {
196         checkPermission();
197         if (!uri.getScheme().equalsIgnoreCase(getScheme())) {
198             throw new IllegalArgumentException("URI does not match this provider");
199         }
200         if (uri.getAuthority() != null) {
201             throw new IllegalArgumentException("Authority component present");
202         }
203         if (uri.getQuery() != null) {
204             throw new IllegalArgumentException("Query component present");
205         }
206         if (uri.getFragment() != null) {
207             throw new IllegalArgumentException("Fragment component present");
208         }
209         String path = uri.getPath();
210         if (path == null || path.charAt(0) != '/' || path.contains("..")) {
211             throw new IllegalArgumentException("Invalid path component");
212         }
213 
214         return getTheFileSystem().getPath("/modules" + path);
215     }
216 
217     private FileSystem getTheFileSystem() {
218         checkPermission();
219         FileSystem fs = this.theFileSystem;
220         if (fs == null) {
221             synchronized (this) {
222                 fs = this.theFileSystem;
223                 if (fs == null) {
224                     try {
225                         // Special constructor call for singleton instance.
226                         this.theFileSystem = fs = new JrtFileSystem(this);
227                     } catch (IOException ioe) {
228                         throw new InternalError(ioe);
229                     }
230                 }
231             }
232         }
233         return fs;
234     }
235 
236     @Override
237     public FileSystem getFileSystem(URI uri) {
238         checkPermission();
239         checkUri(uri);
240         return getTheFileSystem();
241     }
242 
243     // Checks that the given file is a JrtPath
244     static JrtPath toJrtPath(Path path) {
245         Objects.requireNonNull(path, "path");
246         if (!(path instanceof JrtPath)) {
247             throw new ProviderMismatchException();
248         }
249         return (JrtPath) path;
250     }
251 
252     @Override
253     public void checkAccess(Path path, AccessMode... modes) throws IOException {
254         toJrtPath(path).checkAccess(modes);
255     }
256 
257     @Override
258     public Path readSymbolicLink(Path link) throws IOException {
259         return toJrtPath(link).readSymbolicLink();
260     }
261 
262     @Override
263     public void copy(Path src, Path target, CopyOption... options)
264             throws IOException {
265         toJrtPath(src).copy(toJrtPath(target), options);
266     }
267 
268     @Override
269     public void createDirectory(Path path, FileAttribute<?>... attrs)
270             throws IOException {
271         toJrtPath(path).createDirectory(attrs);
272     }
273 
274     @Override
275     public void delete(Path path) throws IOException {
276         toJrtPath(path).delete();
277     }
278 
279     @Override
280     public <V extends FileAttributeView> V
281             getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
282         return JrtFileAttributeView.get(toJrtPath(path), type, options);
283     }
284 
285     @Override
286     public FileStore getFileStore(Path path) throws IOException {
287         return toJrtPath(path).getFileStore();
288     }
289 
290     @Override
291     public boolean isHidden(Path path) {
292         return toJrtPath(path).isHidden();
293     }
294 
295     @Override
296     public boolean isSameFile(Path path, Path other) throws IOException {
297         return toJrtPath(path).isSameFile(other);
298     }
299 
300     @Override
301     public void move(Path src, Path target, CopyOption... options)
302             throws IOException {
303         toJrtPath(src).move(toJrtPath(target), options);
304     }
305 
306     @Override
307     public AsynchronousFileChannel newAsynchronousFileChannel(Path path,
308             Set<? extends OpenOption> options,
309             ExecutorService exec,
310             FileAttribute<?>... attrs)
311             throws IOException {
312         throw new UnsupportedOperationException();
313     }
314 
315     @Override
316     public SeekableByteChannel newByteChannel(Path path,
317             Set<? extends OpenOption> options,
318             FileAttribute<?>... attrs)
319             throws IOException {
320         return toJrtPath(path).newByteChannel(options, attrs);
321     }
322 
323     @Override
324     public DirectoryStream<Path> newDirectoryStream(
325             Path path, Filter<? super Path> filter) throws IOException {
326         return toJrtPath(path).newDirectoryStream(filter);
327     }
328 
329     @Override
330     public FileChannel newFileChannel(Path path,
331             Set<? extends OpenOption> options,
332             FileAttribute<?>... attrs)
333             throws IOException {
334         return toJrtPath(path).newFileChannel(options, attrs);
335     }
336 
337     @Override
338     public InputStream newInputStream(Path path, OpenOption... options)
339             throws IOException {
340         return toJrtPath(path).newInputStream(options);
341     }
342 
343     @Override
344     public OutputStream newOutputStream(Path path, OpenOption... options)
345             throws IOException {
346         return toJrtPath(path).newOutputStream(options);
347     }
348 
349     @Override
350     @SuppressWarnings("unchecked") // Cast to A
351     public <A extends BasicFileAttributes> A
352             readAttributes(Path path, Class<A> type, LinkOption... options)
353             throws IOException {
354         if (type == BasicFileAttributes.class || type == JrtFileAttributes.class) {
355             return (A) toJrtPath(path).getAttributes(options);
356         }
357         return null;
358     }
359 
360     @Override
361     public Map<String, Object>
362             readAttributes(Path path, String attribute, LinkOption... options)
363             throws IOException {
364         return toJrtPath(path).readAttributes(attribute, options);
365     }
366 
367     @Override
368     public void setAttribute(Path path, String attribute,
369             Object value, LinkOption... options)
370             throws IOException {
371         toJrtPath(path).setAttribute(attribute, value, options);
372     }
373 }