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