1 /*
2  * Copyright (c) 2014, 2017, 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.ArrayList;
58 import java.util.Arrays;
59 import java.util.Collections;
60 import java.util.HashSet;
61 import java.util.Iterator;
62 import java.util.Map;
63 import java.util.Objects;
64 import java.util.Set;
65 import java.util.regex.Pattern;
66 import jdk.internal.jimage.ImageReader.Node;
67 import static java.util.stream.Collectors.toList;
68 
69 /**
70  * jrt file system implementation built on System jimage files.
71  *
72  * @implNote This class needs to maintain JDK 8 source compatibility.
73  *
74  * It is used internally in the JDK to implement jimage/jrtfs access,
75  * but also compiled and delivered as part of the jrtfs.jar to support access
76  * to the jimage file provided by the shipped JDK by tools running on JDK 8.
77  */
78 class JrtFileSystem extends FileSystem {
79 
80     private final JrtFileSystemProvider provider;
81     private final JrtPath rootPath = new JrtPath(this, "/");
82     private volatile boolean isOpen;
83     private volatile boolean isClosable;
84     private SystemImage image;
85 
JrtFileSystem(JrtFileSystemProvider provider, Map<String, ?> env)86     JrtFileSystem(JrtFileSystemProvider provider, Map<String, ?> env)
87             throws IOException
88     {
89         this.provider = provider;
90         this.image = SystemImage.open();  // open image file
91         this.isOpen = true;
92         this.isClosable = env != null;
93     }
94 
95     // FileSystem method implementations
96     @Override
isOpen()97     public boolean isOpen() {
98         return isOpen;
99     }
100 
101     @Override
close()102     public void close() throws IOException {
103         if (!isClosable)
104             throw new UnsupportedOperationException();
105         cleanup();
106     }
107 
108     @Override
109     @SuppressWarnings("deprecation")
finalize()110     protected void finalize() throws Throwable {
111         try {
112             cleanup();
113         } catch (IOException ignored) {}
114     }
115 
116     @Override
provider()117     public FileSystemProvider provider() {
118         return provider;
119     }
120 
121     @Override
getRootDirectories()122     public Iterable<Path> getRootDirectories() {
123         return Collections.singleton(getRootPath());
124     }
125 
126     @Override
getPath(String first, String... more)127     public JrtPath getPath(String first, String... more) {
128         if (more.length == 0) {
129             return new JrtPath(this, first);
130         }
131         StringBuilder sb = new StringBuilder();
132         sb.append(first);
133         for (String path : more) {
134             if (!path.isEmpty()) {
135                 if (sb.length() > 0) {
136                     sb.append('/');
137                 }
138                 sb.append(path);
139             }
140         }
141         return new JrtPath(this, sb.toString());
142     }
143 
144     @Override
isReadOnly()145     public final boolean isReadOnly() {
146         return true;
147     }
148 
149     @Override
getUserPrincipalLookupService()150     public final UserPrincipalLookupService getUserPrincipalLookupService() {
151         throw new UnsupportedOperationException();
152     }
153 
154     @Override
newWatchService()155     public final WatchService newWatchService() {
156         throw new UnsupportedOperationException();
157     }
158 
159     @Override
getFileStores()160     public final Iterable<FileStore> getFileStores() {
161         return Collections.singleton(getFileStore(getRootPath()));
162     }
163 
164     private static final Set<String> supportedFileAttributeViews
165             = Collections.unmodifiableSet(
166                     new HashSet<String>(Arrays.asList("basic", "jrt")));
167 
168     @Override
supportedFileAttributeViews()169     public final Set<String> supportedFileAttributeViews() {
170         return supportedFileAttributeViews;
171     }
172 
173     @Override
toString()174     public final String toString() {
175         return "jrt:/";
176     }
177 
178     @Override
getSeparator()179     public final String getSeparator() {
180         return "/";
181     }
182 
183     @Override
getPathMatcher(String syntaxAndInput)184     public PathMatcher getPathMatcher(String syntaxAndInput) {
185         int pos = syntaxAndInput.indexOf(':');
186         if (pos <= 0 || pos == syntaxAndInput.length()) {
187             throw new IllegalArgumentException("pos is " + pos);
188         }
189         String syntax = syntaxAndInput.substring(0, pos);
190         String input = syntaxAndInput.substring(pos + 1);
191         String expr;
192         if (syntax.equalsIgnoreCase("glob")) {
193             expr = JrtUtils.toRegexPattern(input);
194         } else if (syntax.equalsIgnoreCase("regex")) {
195             expr = input;
196         } else {
197                 throw new UnsupportedOperationException("Syntax '" + syntax
198                         + "' not recognized");
199         }
200         // return matcher
201         final Pattern pattern = Pattern.compile(expr);
202         return (Path path) -> pattern.matcher(path.toString()).matches();
203     }
204 
resolveLink(JrtPath path)205     JrtPath resolveLink(JrtPath path) throws IOException {
206         Node node = checkNode(path);
207         if (node.isLink()) {
208             node = node.resolveLink();
209             return new JrtPath(this, node.getName());  // TBD, normalized?
210         }
211         return path;
212     }
213 
getFileAttributes(JrtPath path, LinkOption... options)214     JrtFileAttributes getFileAttributes(JrtPath path, LinkOption... options)
215             throws IOException {
216         Node node = checkNode(path);
217         if (node.isLink() && followLinks(options)) {
218             return new JrtFileAttributes(node.resolveLink(true));
219         }
220         return new JrtFileAttributes(node);
221     }
222 
223     /**
224      * returns the list of child paths of the given directory "path"
225      *
226      * @param path name of the directory whose content is listed
227      * @return iterator for child paths of the given directory path
228      */
iteratorOf(JrtPath path, DirectoryStream.Filter<? super Path> filter)229     Iterator<Path> iteratorOf(JrtPath path, DirectoryStream.Filter<? super Path> filter)
230             throws IOException {
231         Node node = checkNode(path).resolveLink(true);
232         if (!node.isDirectory()) {
233             throw new NotDirectoryException(path.getName());
234         }
235         if (filter == null) {
236             return node.getChildren()
237                        .stream()
238                        .map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName())))
239                        .iterator();
240         }
241         return node.getChildren()
242                    .stream()
243                    .map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName())))
244                    .filter(p ->  { try { return filter.accept(p);
245                                    } catch (IOException x) {}
246                                    return false;
247                                   })
248                    .iterator();
249     }
250 
251     // returns the content of the file resource specified by the path
252     byte[] getFileContent(JrtPath path) throws IOException {
253         Node node = checkNode(path);
254         if (node.isDirectory()) {
255             throw new FileSystemException(path + " is a directory");
256         }
257         //assert node.isResource() : "resource node expected here";
258         return image.getResource(node);
259     }
260 
261     /////////////// Implementation details below this point //////////
262 
263     // static utility methods
264     static ReadOnlyFileSystemException readOnly() {
265         return new ReadOnlyFileSystemException();
266     }
267 
268     // do the supplied options imply that we have to chase symlinks?
269     static boolean followLinks(LinkOption... options) {
270         if (options != null) {
271             for (LinkOption lo : options) {
272                 Objects.requireNonNull(lo);
273                 if (lo == LinkOption.NOFOLLOW_LINKS) {
274                     return false;
275                 } else {
276                     throw new AssertionError("should not reach here");
277                 }
278             }
279         }
280         return true;
281     }
282 
283     // check that the options passed are supported by (read-only) jrt file system
284     static void checkOptions(Set<? extends OpenOption> options) {
285         // check for options of null type and option is an intance of StandardOpenOption
286         for (OpenOption option : options) {
287             Objects.requireNonNull(option);
288             if (!(option instanceof StandardOpenOption)) {
289                 throw new IllegalArgumentException(
290                     "option class: " + option.getClass());
291             }
292         }
293         if (options.contains(StandardOpenOption.WRITE) ||
294             options.contains(StandardOpenOption.APPEND)) {
295             throw readOnly();
296         }
297     }
298 
299     // clean up this file system - called from finalize and close
300     synchronized void cleanup() throws IOException {
301         if (isOpen) {
302             isOpen = false;
303             image.close();
304             image = null;
305         }
306     }
307 
308     // These methods throw read only file system exception
309     final void setTimes(JrtPath jrtPath, FileTime mtime, FileTime atime, FileTime ctime)
310             throws IOException {
311         throw readOnly();
312     }
313 
314     // These methods throw read only file system exception
315     final void createDirectory(JrtPath jrtPath, FileAttribute<?>... attrs) throws IOException {
316         throw readOnly();
317     }
318 
319     final void deleteFile(JrtPath jrtPath, boolean failIfNotExists)
320             throws IOException {
321         throw readOnly();
322     }
323 
324     final OutputStream newOutputStream(JrtPath jrtPath, OpenOption... options)
325             throws IOException {
326         throw readOnly();
327     }
328 
329     final void copyFile(boolean deletesrc, JrtPath srcPath, JrtPath dstPath, CopyOption... options)
330             throws IOException {
331         throw readOnly();
332     }
333 
334     final FileChannel newFileChannel(JrtPath path,
335             Set<? extends OpenOption> options,
336             FileAttribute<?>... attrs)
337             throws IOException {
338         throw new UnsupportedOperationException("newFileChannel");
339     }
340 
341     final InputStream newInputStream(JrtPath path) throws IOException {
342         return new ByteArrayInputStream(getFileContent(path));
343     }
344 
345     final SeekableByteChannel newByteChannel(JrtPath path,
346             Set<? extends OpenOption> options,
347             FileAttribute<?>... attrs)
348             throws IOException {
349         checkOptions(options);
350 
351         byte[] buf = getFileContent(path);
352         final ReadableByteChannel rbc
353                 = Channels.newChannel(new ByteArrayInputStream(buf));
354         final long size = buf.length;
355         return new SeekableByteChannel() {
356             long read = 0;
357 
358             @Override
359             public boolean isOpen() {
360                 return rbc.isOpen();
361             }
362 
363             @Override
364             public long position() throws IOException {
365                 return read;
366             }
367 
368             @Override
369             public SeekableByteChannel position(long pos)
370                     throws IOException {
371                 throw new UnsupportedOperationException();
372             }
373 
374             @Override
375             public int read(ByteBuffer dst) throws IOException {
376                 int n = rbc.read(dst);
377                 if (n > 0) {
378                     read += n;
379                 }
380                 return n;
381             }
382 
383             @Override
384             public SeekableByteChannel truncate(long size)
385                     throws IOException {
386                 throw new NonWritableChannelException();
387             }
388 
389             @Override
390             public int write(ByteBuffer src) throws IOException {
391                 throw new NonWritableChannelException();
392             }
393 
394             @Override
395             public long size() throws IOException {
396                 return size;
397             }
398 
399             @Override
400             public void close() throws IOException {
401                 rbc.close();
402             }
403         };
404     }
405 
406     final JrtFileStore getFileStore(JrtPath path) {
407         return new JrtFileStore(path);
408     }
409 
410     final void ensureOpen() throws IOException {
411         if (!isOpen()) {
412             throw new ClosedFileSystemException();
413         }
414     }
415 
416     final JrtPath getRootPath() {
417         return rootPath;
418     }
419 
420     boolean isSameFile(JrtPath path1, JrtPath path2) throws IOException {
421         return checkNode(path1) == checkNode(path2);
422     }
423 
424     boolean isLink(JrtPath path) throws IOException {
425         return checkNode(path).isLink();
426     }
427 
428     boolean exists(JrtPath path) throws IOException {
429         try {
430             checkNode(path);
431         } catch (NoSuchFileException exp) {
432             return false;
433         }
434         return true;
435     }
436 
437     boolean isDirectory(JrtPath path, boolean resolveLinks)
438             throws IOException {
439         Node node = checkNode(path);
440         return resolveLinks && node.isLink()
441                 ? node.resolveLink(true).isDirectory()
442                 : node.isDirectory();
443     }
444 
445     JrtPath toRealPath(JrtPath path, LinkOption... options)
446             throws IOException {
447         Node node = checkNode(path);
448         if (followLinks(options) && node.isLink()) {
449             node = node.resolveLink();
450         }
451         // image node holds the real/absolute path name
452         return new JrtPath(this, node.getName(), true);
453     }
454 
455     private Node lookup(String path) {
456         try {
457             return image.findNode(path);
458         } catch (RuntimeException | IOException ex) {
459             throw new InvalidPathException(path, ex.toString());
460         }
461     }
462 
463     private Node lookupSymbolic(String path) {
464         int i = 1;
465         while (i < path.length()) {
466             i = path.indexOf('/', i);
467             if (i == -1) {
468                 break;
469             }
470             String prefix = path.substring(0, i);
471             Node node = lookup(prefix);
472             if (node == null) {
473                 break;
474             }
475             if (node.isLink()) {
476                 Node link = node.resolveLink(true);
477                 // resolved symbolic path concatenated to the rest of the path
478                 String resPath = link.getName() + path.substring(i);
479                 node = lookup(resPath);
480                 return node != null ? node : lookupSymbolic(resPath);
481             }
482             i++;
483         }
484         return null;
485     }
486 
487     Node checkNode(JrtPath path) throws IOException {
488         ensureOpen();
489         String p = path.getResolvedPath();
490         Node node = lookup(p);
491         if (node == null) {
492             node = lookupSymbolic(p);
493             if (node == null) {
494                 throw new NoSuchFileException(p);
495             }
496         }
497         return node;
498     }
499 }
500