| /* | |
| * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package jdk.internal.jrtfs; | |
| import java.io.ByteArrayInputStream; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.io.OutputStream; | |
| import java.nio.ByteBuffer; | |
| import java.nio.channels.Channels; | |
| import java.nio.channels.FileChannel; | |
| import java.nio.channels.NonWritableChannelException; | |
| import java.nio.channels.ReadableByteChannel; | |
| import java.nio.channels.SeekableByteChannel; | |
| import java.nio.file.ClosedFileSystemException; | |
| import java.nio.file.CopyOption; | |
| import java.nio.file.DirectoryStream; | |
| import java.nio.file.FileStore; | |
| import java.nio.file.FileSystem; | |
| import java.nio.file.FileSystemException; | |
| import java.nio.file.InvalidPathException; | |
| import java.nio.file.LinkOption; | |
| import java.nio.file.NoSuchFileException; | |
| import java.nio.file.NotDirectoryException; | |
| import java.nio.file.OpenOption; | |
| import java.nio.file.Path; | |
| import java.nio.file.PathMatcher; | |
| import java.nio.file.ReadOnlyFileSystemException; | |
| import java.nio.file.StandardOpenOption; | |
| import java.nio.file.WatchService; | |
| import java.nio.file.attribute.FileAttribute; | |
| import java.nio.file.attribute.FileTime; | |
| import java.nio.file.attribute.UserPrincipalLookupService; | |
| import java.nio.file.spi.FileSystemProvider; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.Collections; | |
| import java.util.HashSet; | |
| import java.util.Iterator; | |
| import java.util.Map; | |
| import java.util.Objects; | |
| import java.util.Set; | |
| import java.util.regex.Pattern; | |
| import jdk.internal.jimage.ImageReader.Node; | |
| import static java.util.stream.Collectors.toList; | |
| /** | |
| * jrt file system implementation built on System jimage files. | |
| * | |
| * @implNote This class needs to maintain JDK 8 source compatibility. | |
| * | |
| * It is used internally in the JDK to implement jimage/jrtfs access, | |
| * but also compiled and delivered as part of the jrtfs.jar to support access | |
| * to the jimage file provided by the shipped JDK by tools running on JDK 8. | |
| */ | |
| class JrtFileSystem extends FileSystem { | |
| private final JrtFileSystemProvider provider; | |
| private final JrtPath rootPath = new JrtPath(this, "/"); | |
| private volatile boolean isOpen; | |
| private volatile boolean isClosable; | |
| private SystemImage image; | |
| JrtFileSystem(JrtFileSystemProvider provider, Map<String, ?> env) | |
| throws IOException | |
| { | |
| this.provider = provider; | |
| this.image = SystemImage.open(); // open image file | |
| this.isOpen = true; | |
| this.isClosable = env != null; | |
| } | |
| // FileSystem method implementations | |
| @Override | |
| public boolean isOpen() { | |
| return isOpen; | |
| } | |
| @Override | |
| public void close() throws IOException { | |
| if (!isClosable) | |
| throw new UnsupportedOperationException(); | |
| cleanup(); | |
| } | |
| @Override | |
| public FileSystemProvider provider() { | |
| return provider; | |
| } | |
| @Override | |
| public Iterable<Path> getRootDirectories() { | |
| return Collections.singleton(getRootPath()); | |
| } | |
| @Override | |
| public JrtPath getPath(String first, String... more) { | |
| if (more.length == 0) { | |
| return new JrtPath(this, first); | |
| } | |
| StringBuilder sb = new StringBuilder(); | |
| sb.append(first); | |
| for (String path : more) { | |
| if (!path.isEmpty()) { | |
| if (sb.length() > 0) { | |
| sb.append('/'); | |
| } | |
| sb.append(path); | |
| } | |
| } | |
| return new JrtPath(this, sb.toString()); | |
| } | |
| @Override | |
| public final boolean isReadOnly() { | |
| return true; | |
| } | |
| @Override | |
| public final UserPrincipalLookupService getUserPrincipalLookupService() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Override | |
| public final WatchService newWatchService() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Override | |
| public final Iterable<FileStore> getFileStores() { | |
| return Collections.singleton(getFileStore(getRootPath())); | |
| } | |
| private static final Set<String> supportedFileAttributeViews | |
| = Collections.unmodifiableSet( | |
| new HashSet<String>(Arrays.asList("basic", "jrt"))); | |
| @Override | |
| public final Set<String> supportedFileAttributeViews() { | |
| return supportedFileAttributeViews; | |
| } | |
| @Override | |
| public final String toString() { | |
| return "jrt:/"; | |
| } | |
| @Override | |
| public final String getSeparator() { | |
| return "/"; | |
| } | |
| @Override | |
| public PathMatcher getPathMatcher(String syntaxAndInput) { | |
| int pos = syntaxAndInput.indexOf(':'); | |
| if (pos <= 0) { | |
| throw new IllegalArgumentException(); | |
| } | |
| String syntax = syntaxAndInput.substring(0, pos); | |
| String input = syntaxAndInput.substring(pos + 1); | |
| String expr; | |
| if (syntax.equalsIgnoreCase("glob")) { | |
| expr = JrtUtils.toRegexPattern(input); | |
| } else if (syntax.equalsIgnoreCase("regex")) { | |
| expr = input; | |
| } else { | |
| throw new UnsupportedOperationException("Syntax '" + syntax | |
| + "' not recognized"); | |
| } | |
| // return matcher | |
| final Pattern pattern = Pattern.compile(expr); | |
| return (Path path) -> pattern.matcher(path.toString()).matches(); | |
| } | |
| JrtPath resolveLink(JrtPath path) throws IOException { | |
| Node node = checkNode(path); | |
| if (node.isLink()) { | |
| node = node.resolveLink(); | |
| return new JrtPath(this, node.getName()); // TBD, normalized? | |
| } | |
| return path; | |
| } | |
| JrtFileAttributes getFileAttributes(JrtPath path, LinkOption... options) | |
| throws IOException { | |
| Node node = checkNode(path); | |
| if (node.isLink() && followLinks(options)) { | |
| return new JrtFileAttributes(node.resolveLink(true)); | |
| } | |
| return new JrtFileAttributes(node); | |
| } | |
| /** | |
| * returns the list of child paths of the given directory "path" | |
| * | |
| * @param path name of the directory whose content is listed | |
| * @return iterator for child paths of the given directory path | |
| */ | |
| Iterator<Path> iteratorOf(JrtPath path, DirectoryStream.Filter<? super Path> filter) | |
| throws IOException { | |
| Node node = checkNode(path).resolveLink(true); | |
| if (!node.isDirectory()) { | |
| throw new NotDirectoryException(path.getName()); | |
| } | |
| if (filter == null) { | |
| return node.getChildren() | |
| .stream() | |
| .map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName()))) | |
| .iterator(); | |
| } | |
| return node.getChildren() | |
| .stream() | |
| .map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName()))) | |
| .filter(p -> { try { return filter.accept(p); | |
| } catch (IOException x) {} | |
| return false; | |
| }) | |
| .iterator(); | |
| } | |
| // returns the content of the file resource specified by the path | |
| byte[] getFileContent(JrtPath path) throws IOException { | |
| Node node = checkNode(path); | |
| if (node.isDirectory()) { | |
| throw new FileSystemException(path + " is a directory"); | |
| } | |
| //assert node.isResource() : "resource node expected here"; | |
| return image.getResource(node); | |
| } | |
| /////////////// Implementation details below this point ////////// | |
| // static utility methods | |
| static ReadOnlyFileSystemException readOnly() { | |
| return new ReadOnlyFileSystemException(); | |
| } | |
| // do the supplied options imply that we have to chase symlinks? | |
| static boolean followLinks(LinkOption... options) { | |
| if (options != null) { | |
| for (LinkOption lo : options) { | |
| Objects.requireNonNull(lo); | |
| if (lo == LinkOption.NOFOLLOW_LINKS) { | |
| return false; | |
| } else { | |
| throw new AssertionError("should not reach here"); | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| // check that the options passed are supported by (read-only) jrt file system | |
| static void checkOptions(Set<? extends OpenOption> options) { | |
| // check for options of null type and option is an instance of StandardOpenOption | |
| for (OpenOption option : options) { | |
| Objects.requireNonNull(option); | |
| if (!(option instanceof StandardOpenOption)) { | |
| throw new IllegalArgumentException( | |
| "option class: " + option.getClass()); | |
| } | |
| } | |
| if (options.contains(StandardOpenOption.WRITE) || | |
| options.contains(StandardOpenOption.APPEND)) { | |
| throw readOnly(); | |
| } | |
| } | |
| // clean up this file system - called from finalize and close | |
| synchronized void cleanup() throws IOException { | |
| if (isOpen) { | |
| isOpen = false; | |
| image.close(); | |
| image = null; | |
| } | |
| } | |
| // These methods throw read only file system exception | |
| final void setTimes(JrtPath jrtPath, FileTime mtime, FileTime atime, FileTime ctime) | |
| throws IOException { | |
| throw readOnly(); | |
| } | |
| // These methods throw read only file system exception | |
| final void createDirectory(JrtPath jrtPath, FileAttribute<?>... attrs) throws IOException { | |
| throw readOnly(); | |
| } | |
| final void deleteFile(JrtPath jrtPath, boolean failIfNotExists) | |
| throws IOException { | |
| throw readOnly(); | |
| } | |
| final OutputStream newOutputStream(JrtPath jrtPath, OpenOption... options) | |
| throws IOException { | |
| throw readOnly(); | |
| } | |
| final void copyFile(boolean deletesrc, JrtPath srcPath, JrtPath dstPath, CopyOption... options) | |
| throws IOException { | |
| throw readOnly(); | |
| } | |
| final FileChannel newFileChannel(JrtPath path, | |
| Set<? extends OpenOption> options, | |
| FileAttribute<?>... attrs) | |
| throws IOException { | |
| throw new UnsupportedOperationException("newFileChannel"); | |
| } | |
| final InputStream newInputStream(JrtPath path) throws IOException { | |
| return new ByteArrayInputStream(getFileContent(path)); | |
| } | |
| final SeekableByteChannel newByteChannel(JrtPath path, | |
| Set<? extends OpenOption> options, | |
| FileAttribute<?>... attrs) | |
| throws IOException { | |
| checkOptions(options); | |
| byte[] buf = getFileContent(path); | |
| final ReadableByteChannel rbc | |
| = Channels.newChannel(new ByteArrayInputStream(buf)); | |
| final long size = buf.length; | |
| return new SeekableByteChannel() { | |
| long read = 0; | |
| @Override | |
| public boolean isOpen() { | |
| return rbc.isOpen(); | |
| } | |
| @Override | |
| public long position() throws IOException { | |
| return read; | |
| } | |
| @Override | |
| public SeekableByteChannel position(long pos) | |
| throws IOException { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Override | |
| public int read(ByteBuffer dst) throws IOException { | |
| int n = rbc.read(dst); | |
| if (n > 0) { | |
| read += n; | |
| } | |
| return n; | |
| } | |
| @Override | |
| public SeekableByteChannel truncate(long size) | |
| throws IOException { | |
| throw new NonWritableChannelException(); | |
| } | |
| @Override | |
| public int write(ByteBuffer src) throws IOException { | |
| throw new NonWritableChannelException(); | |
| } | |
| @Override | |
| public long size() throws IOException { | |
| return size; | |
| } | |
| @Override | |
| public void close() throws IOException { | |
| rbc.close(); | |
| } | |
| }; | |
| } | |
| final JrtFileStore getFileStore(JrtPath path) { | |
| return new JrtFileStore(path); | |
| } | |
| final void ensureOpen() throws IOException { | |
| if (!isOpen()) { | |
| throw new ClosedFileSystemException(); | |
| } | |
| } | |
| final JrtPath getRootPath() { | |
| return rootPath; | |
| } | |
| boolean isSameFile(JrtPath path1, JrtPath path2) throws IOException { | |
| return checkNode(path1) == checkNode(path2); | |
| } | |
| boolean isLink(JrtPath path) throws IOException { | |
| return checkNode(path).isLink(); | |
| } | |
| boolean exists(JrtPath path) throws IOException { | |
| try { | |
| checkNode(path); | |
| } catch (NoSuchFileException exp) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| boolean isDirectory(JrtPath path, boolean resolveLinks) | |
| throws IOException { | |
| Node node = checkNode(path); | |
| return resolveLinks && node.isLink() | |
| ? node.resolveLink(true).isDirectory() | |
| : node.isDirectory(); | |
| } | |
| JrtPath toRealPath(JrtPath path, LinkOption... options) | |
| throws IOException { | |
| Node node = checkNode(path); | |
| if (followLinks(options) && node.isLink()) { | |
| node = node.resolveLink(); | |
| } | |
| // image node holds the real/absolute path name | |
| return new JrtPath(this, node.getName(), true); | |
| } | |
| private Node lookup(String path) { | |
| try { | |
| return image.findNode(path); | |
| } catch (RuntimeException | IOException ex) { | |
| throw new InvalidPathException(path, ex.toString()); | |
| } | |
| } | |
| private Node lookupSymbolic(String path) { | |
| int i = 1; | |
| while (i < path.length()) { | |
| i = path.indexOf('/', i); | |
| if (i == -1) { | |
| break; | |
| } | |
| String prefix = path.substring(0, i); | |
| Node node = lookup(prefix); | |
| if (node == null) { | |
| break; | |
| } | |
| if (node.isLink()) { | |
| Node link = node.resolveLink(true); | |
| // resolved symbolic path concatenated to the rest of the path | |
| String resPath = link.getName() + path.substring(i); | |
| node = lookup(resPath); | |
| return node != null ? node : lookupSymbolic(resPath); | |
| } | |
| i++; | |
| } | |
| return null; | |
| } | |
| Node checkNode(JrtPath path) throws IOException { | |
| ensureOpen(); | |
| String p = path.getResolvedPath(); | |
| Node node = lookup(p); | |
| if (node == null) { | |
| node = lookupSymbolic(p); | |
| if (node == null) { | |
| throw new NoSuchFileException(p); | |
| } | |
| } | |
| return node; | |
| } | |
| } |