/* * Copyright 2025 JetBrains s.r.o. * 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 java.io; import com.jetbrains.internal.IoOverNio; import com.jetbrains.internal.IoToNioErrorMessageHolder; import jdk.internal.misc.VM; import sun.nio.ch.FileChannelImpl; import java.nio.channels.FileChannel; import java.nio.file.AccessDeniedException; import java.nio.file.AccessMode; import java.nio.file.DirectoryStream; import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileStore; import java.nio.file.FileSystemException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.LinkOption; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.DosFileAttributeView; import java.nio.file.attribute.DosFileAttributes; import java.nio.file.attribute.FileTime; import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.PosixFilePermission; import java.security.AccessControlException; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.Set; import static com.jetbrains.internal.IoOverNio.DEBUG; class IoOverNioFileSystem extends FileSystem { /** * The filesystem created by default in the original OpenJDK. */ private final FileSystem parent; IoOverNioFileSystem(FileSystem parent) { this.parent = parent; } /** * If this method returns a non-null {@link java.nio.file.FileSystem}, * then {@code java.io} primitives must use this filesystem for all operations inside wherever it is possible. * Otherwise, {@link java.io} must use legacy functions for accessing the filesystem. * It is allowed to not use NIO instead of the original java.io code * if some function is not implemented in NIO. */ static java.nio.file.FileSystem acquireNioFs(String path) { if (!VM.isBooted()) { return null; } if (!IoOverNio.IS_ENABLED_IN_GENERAL) { return null; } if (!IoOverNio.isAllowedInThisThread()) { return null; } java.nio.file.FileSystem nioFs = FileSystems.getDefault(); if (nioFs.getSeparator().equals("\\") && Objects.equals(path, "NUL:")) { return null; } return nioFs; } /** * To be used in {@link java.io.FileInputStream} and so on. * Constructors of these classes may throw only {@link FileNotFoundException} whatever error actually happens. */ static FileNotFoundException convertNioToIoExceptionInStreams(IOException source) { if (source instanceof FileNotFoundException nsfe) { return nsfe; } String message = IoToNioErrorMessageHolder.removeMessage(source); if (source instanceof FileSystemException s && s.getFile() != null) { if (message == null) { message = s.getFile(); } else { message = s.getFile() + " (" + message + ")"; } } FileNotFoundException result = new FileNotFoundException(message); result.initCause(source); return result; } private static boolean setPermission0(java.nio.file.FileSystem nioFs, File f, int access, boolean enable, boolean owneronly) { if (f.getPath().isEmpty()) { if (nioFs.getSeparator().equals("\\")) { // This behavior is hardcoded in Java_java_io_WinNTFileSystem_setPermission0, // and this check happens there before calling GetFileAttributesW. return (ACCESS_READ == access || ACCESS_EXECUTE == access) && enable; } else { // Unlike on Windows, // Java_java_io_UnixFileSystem_checkAccess0 has no return statements before chmod(), // and chmod() returns an error for an empty path. return false; } } Path path; try { path = nioFs.getPath(f.getPath()); } catch (InvalidPathException err) { throw new InternalError(err.getMessage(), err); } // See the comment inside getBooleanAttributes that explains the order of file attribute view classes. BasicFileAttributeView view; try { view = Files.getFileAttributeView(path, PosixFileAttributeView.class); } catch (UnsupportedOperationException ignored) { view = null; } if (view == null) { try { view = Files.getFileAttributeView(path, DosFileAttributeView.class); } catch (UnsupportedOperationException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("File system %s supports neither posix nor dos attributes", nioFs), err) .printStackTrace(System.err); } return false; } } boolean result = true; if (view instanceof DosFileAttributeView dosView) { result = false; try { // Both the original java.io.File.setReadOnly // and sun.nio.fs.WindowsFileAttributeViews.Dos.setReadOnly // use SetFileAttributesW. // // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesw // The documentation states: // > This attribute is not honored on directories. // However, SetFileAttributesW always returns 0, not changing anything. // In java.io.File (see Java_java_io_WinNTFileSystem_setReadOnly0), there's an explicit check // if it is a directory. // On the contrary, there's no such check in WindowsFileAttributeViews. if ((access & ACCESS_WRITE) != 0 && !dosView.readAttributes().isDirectory()) { dosView.setReadOnly(!enable); result = true; } else if (access == ACCESS_READ || access == ACCESS_EXECUTE) { result = enable; // Like in Java_java_io_WinNTFileSystem_setPermission0 } } catch (IOException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("Can't set read only attributes for %s", f), err) .printStackTrace(System.err); } } } else if (view instanceof PosixFileAttributeView posixView) { try { Set perms = posixView.readAttributes().permissions(); if ((access & ACCESS_READ) != 0) { if (enable) { perms.add(PosixFilePermission.OWNER_READ); } else { perms.remove(PosixFilePermission.OWNER_READ); } if (!owneronly) { if (enable) { perms.add(PosixFilePermission.GROUP_READ); perms.add(PosixFilePermission.OTHERS_READ); } else { perms.remove(PosixFilePermission.GROUP_READ); perms.remove(PosixFilePermission.OTHERS_READ); } } } if ((access & ACCESS_WRITE) != 0) { if (enable) { perms.add(PosixFilePermission.OWNER_WRITE); } else { perms.remove(PosixFilePermission.OWNER_WRITE); } if (!owneronly) { if (enable) { perms.add(PosixFilePermission.GROUP_WRITE); perms.add(PosixFilePermission.OTHERS_WRITE); } else { perms.remove(PosixFilePermission.GROUP_WRITE); perms.remove(PosixFilePermission.OTHERS_WRITE); } } } if ((access & ACCESS_EXECUTE) != 0) { if (enable) { perms.add(PosixFilePermission.OWNER_EXECUTE); } else { perms.remove(PosixFilePermission.OWNER_EXECUTE); } if (!owneronly) { if (enable) { perms.add(PosixFilePermission.GROUP_EXECUTE); perms.add(PosixFilePermission.OTHERS_EXECUTE); } else { perms.remove(PosixFilePermission.GROUP_EXECUTE); perms.remove(PosixFilePermission.OTHERS_EXECUTE); } } } posixView.setPermissions(perms); } catch (IOException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("Can't set posix attributes for %s", f), err) .printStackTrace(System.err); } result = false; } } return result; } record StreamInitializationBundle( FileDescriptor fd, FileChannel channel, ExternalChannelHolder externalChannelHolder ) { } static StreamInitializationBundle initializeStreamUsingNio( Closeable owner, java.nio.file.FileSystem nioFs, File file, Path nioPath, Set optionsForChannel, NioChannelCleanable channelCleanable) throws FileNotFoundException { try { // This tricky thread local variable allows specifying an argument for sun.nio.ch.FileChannelImpl. // which is not present in the NIO public API and which is not easy to specify another way. IoOverNio.PARENT_FOR_FILE_CHANNEL_IMPL.set(owner); return initializeStreamsUsingNio0(owner, nioFs, file, nioPath, optionsForChannel, channelCleanable); } finally { IoOverNio.PARENT_FOR_FILE_CHANNEL_IMPL.remove(); } } private static StreamInitializationBundle initializeStreamsUsingNio0( Closeable owner, java.nio.file.FileSystem nioFs, File file, Path nioPath, Set optionsForChannel, NioChannelCleanable channelCleanable) throws FileNotFoundException { FileChannel channel = null; try { channel = nioFs.provider().newFileChannel(nioPath, optionsForChannel); return new StreamInitializationBundle( initializeFdUsingNio(owner, channel, channelCleanable), channel, new ExternalChannelHolder(owner, channel, channelCleanable) ); } catch (IOException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("Can't create a %s for %s with %s", owner.getClass().getSimpleName(), file, nioFs), err) .printStackTrace(System.err); } if (channel != null) { try { channel.close(); } catch (IOException ignored) { // Nothing. } } throw IoOverNioFileSystem.convertNioToIoExceptionInStreams(err); } } private static FileDescriptor initializeFdUsingNio( Closeable owner, FileChannel channel, NioChannelCleanable channelCleanable) { final FileDescriptor fd; if (channel instanceof FileChannelImpl fci) { fd = fci.getFD(); fd.attach(owner); FileCleanable.register(fd); fci.setUninterruptible(); } else { channelCleanable.setChannel(channel); fd = new FileDescriptor(); fd.registerCleanup(new NoOpCleanable(fd)); } return fd; } @Override public char getSeparator() { return parent.getSeparator(); } @Override public char getPathSeparator() { return parent.getPathSeparator(); } @Override public String normalize(String path) { // java.nio.file.Path.normalize works differently compared to this normalizer. // Especially, Path.normalize converts "." into an empty string, which breaks // even tests in OpenJDK. return parent.normalize(path); } @Override public int prefixLength(String path) { // Although it's possible to represent this function via `Path.getRoot`, // the default file system and java.nio handle corner cases with incorrect paths differently. return parent.prefixLength(path); } @Override public String resolve(String parent, String child) { // java.nio is stricter to various invalid symbols than java.io. return this.parent.resolve(parent, child); } @Override public String getDefaultParent() { @SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(null); if (nioFs != null) { return nioFs.getSeparator(); } return parent.getDefaultParent(); } @Override public String fromURIPath(String path) { return parent.fromURIPath(path); } @Override public boolean isAbsolute(File f) { boolean result = isAbsolute0(f); if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.isAbsolute(%s) = %b%n", f, result); } return result; } private boolean isAbsolute0(File f) { @SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath()); if (nioFs != null) { try { return nioFs.getPath(f.getPath()).isAbsolute(); } catch (InvalidPathException err) { // Path parsing in java.nio is stricter than in java.io. // Giving a chance to the original implementation. if (DEBUG.writeErrors()) { new Throwable(String.format("Can't check if a path %s is absolute", f), err) .printStackTrace(System.err); } } } return parent.isAbsolute(f); } @Override public boolean isInvalid(File f) { @SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath()); if (nioFs != null) { if (nioFs.getSeparator().equals("\\") && f.getPath().equals("NUL:")) { return false; } try { Path ignored = nioFs.getPath(f.getPath()); return false; } catch (InvalidPathException ignored) { return true; } } return parent.isInvalid(f); } @Override public String resolve(File f) { try { String result = resolve0(f); if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.resolve(%s) = %s%n", f, result); } return result; } catch (RuntimeException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("IoOverNioFileSystem.resolve(%s) threw an error%n", f), err) .printStackTrace(System.err); } throw err; } } private String resolve0(File f) { @SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath()); if (nioFs != null) { String initialPath = f.toString(); if (getSeparator() == '\\' && prefixLength(initialPath) == 2 && initialPath.charAt(0) == '\\') { // This behavior is covered by `test/jdk/java/io/File/GetAbsolutePath.java`. // If this condition passed through Path.toAbsolutePath, an additional excessive slash // might be added at the end, and the behavior would differ from the original java.io.File.getAbsolutePath. return initialPath; } try { String resolvedPath = nioFs.getPath(f.getPath()).toAbsolutePath().toString(); if ( initialPath.length() > 2 && getSeparator() == '\\' && initialPath.charAt(1) == ':' && initialPath.charAt(2) != '\\' && initialPath.charAt(0) != System.getProperty("user.dir", "?").charAt(0) ) { // The source is a tricky drive-relative path, // and the current working directory is on a different drive. // For unclear reasons, the resolved path must have two backslashes after the colon. // This behavior is covered by `test/jdk/java/io/File/GetAbsolutePath.java`. resolvedPath = resolvedPath.substring(0, 2) + '\\' + resolvedPath.substring(2); } return resolvedPath; } catch (InvalidPathException err) { // Path parsing in java.nio is stricter than in java.io. // Giving a chance to the original implementation. if (DEBUG.writeErrors()) { new Throwable(String.format("Can't resolve a path %s", f), err) .printStackTrace(System.err); } } } return parent.resolve(f); } @Override public String canonicalize(String path) throws IOException { try { String result = canonicalize0(path); if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.canonicalize(%s) = %s%n", path, result); } return result; } catch (RuntimeException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("IoOverNioFileSystem.canonicalize(%s) threw an error%n", path), err) .printStackTrace(System.err); } throw err; } } private String canonicalize0(String path) throws IOException { @SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(path); if (nioFs != null) { try { // Unlike java.nio.file.Path.toRealPath, File.getCanonicalFile works with non-existent files // and resolves symlinks for the first existing parent. Path nioPath = nioFs.getPath(path); if (!nioPath.isAbsolute()) { nioPath = Path.of(System.getProperty("user.dir")).resolve(nioPath); } boolean isTrickyDriveRelativePath = !nioPath.toString().startsWith(nioPath.getRoot().toString()); Path suffix = nioFs.getPath(""); while (!nioPath.equals(nioPath.getRoot())) { Path parent = nioPath.getParent(); if (parent == null) { parent = nioPath.getRoot(); } String fileNameStr = nioPath.getFileName().toString(); if (!fileNameStr.isEmpty() && !fileNameStr.equals(".")) { try { nioPath = nioPath.toRealPath(); break; } catch (IOException ignored) { // Nothing. } suffix = nioPath.getFileName().resolve(suffix); } nioPath = parent; } Path result = nioPath.resolve(suffix).normalize(); // It's not clear why it should work like that, // but this behaviour is checked by the test `GetAbsolutePath.windowsDriveRelative`. if (isTrickyDriveRelativePath) { StringBuilder sb = new StringBuilder(result.getRoot().toString()); for (int i = 0; i < result.getNameCount(); i++) { sb.append(getSeparator()); sb.append(result.getName(i).toString()); } return sb.toString(); } else { return result.toString(); } } catch (InvalidPathException err) { // Path parsing in java.nio is stricter than in java.io. // Giving a chance to the original implementation. if (DEBUG.writeErrors()) { new Throwable(String.format("Can't canonicalize a path %s", path), err) .printStackTrace(System.err); } } } return parent.canonicalize(path); } @Override public boolean hasBooleanAttributes(File f, int attributes) { try { boolean result = hasBooleanAttributes0(f, attributes); if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.hasBooleanAttributes(%s, %s) = %b%n", f, attributes, result); } return result; } catch (RuntimeException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("IoOverNioFileSystem.hasBooleanAttributes(%s, %s) threw an error", f, attributes), err) .printStackTrace(System.err); } throw err; } } @SuppressWarnings("resource") private boolean hasBooleanAttributes0(File f, int attributes) { boolean result; if (acquireNioFs(f.getPath()) != null) { result = (getBooleanAttributes0(f) & attributes) == attributes; } else { result = parent.hasBooleanAttributes(f, attributes); } if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.hasBooleanAttributes(%s, %s) = %b%n", f, attributes, result); } return result; } @Override public int getBooleanAttributes(File f) { int result = getBooleanAttributes0(f); if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.getBooleanAttributes(%s) = %d%n", f, result); } return result; } private int getBooleanAttributes0(File f) { @SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath()); if (nioFs != null) { try { Path path = nioFs.getPath(f.getPath()); { boolean isAbsoluteOrDriveRelative; if (path.isAbsolute()) { isAbsoluteOrDriveRelative = true; } else if (getSeparator() == '\\') { String pathString = path.toString(); isAbsoluteOrDriveRelative = switch (pathString.length()) { case 0 -> false; case 1 -> pathString.charAt(0) == '\\'; case 2 -> Character.isLetter(pathString.charAt(0)) && pathString.charAt(1) == ':'; default -> Character.isLetter(pathString.charAt(0)) && pathString.charAt(1) == ':' && pathString.charAt(2) != '\\'; }; } else { isAbsoluteOrDriveRelative = false; } if (!isAbsoluteOrDriveRelative && (path.getFileName() == null || path.getFileName().toString().isEmpty())) { // The LibC function `stat` returns an error for such calls. return 0; } } // The order of checking Posix attributes first and DOS attributes next is deliberate. // WindowsFileSystem supports these attributes: basic, dos, acl, owner, user. // LinuxFileSystem supports: basic, posix, unix, owner, dos, user. // MacOSFileSystem and BsdFileSystem support: basic, posix, unix, owner, user. // // Notice that Linux FS supports DOS attributes, which is unexpected. // The DOS adapter implementation from LinuxFileSystem uses `open` for getting attributes. // It's not only more expensive than `stat`, but also doesn't work with Unix sockets. // // Also, notice that Windows FS does not support Posix attributes, which is expected. // Checking for Posix attributes first prevents from checking DOS attributes on Linux, // even though Posix permissions aren't used in this method. BasicFileAttributes attrs = null; Set supportedFileAttributeViews = path.getFileSystem().supportedFileAttributeViews(); if (supportedFileAttributeViews.contains("posix")) { try { attrs = Files.readAttributes(path, PosixFileAttributes.class); } catch (UnsupportedOperationException | SecurityException ignored) { // Nothing. } } if (attrs == null && supportedFileAttributeViews.contains("dos")) { try { attrs = Files.readAttributes(path, DosFileAttributes.class); } catch (UnsupportedOperationException | SecurityException ignored) { // Nothing. } } if (attrs == null) { attrs = Files.readAttributes(path, BasicFileAttributes.class); } return BA_EXISTS | (attrs.isDirectory() ? BA_DIRECTORY : 0) | (attrs.isRegularFile() ? BA_REGULAR : 0) | (attrs instanceof DosFileAttributes dosAttrs && dosAttrs.isHidden() ? BA_HIDDEN : 0) // Just like in java.io.UnixFileSystem.isHidden | (attrs instanceof PosixFileAttributes && f.getName().startsWith(".") ? BA_HIDDEN : 0); } catch (InvalidPathException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("Can't get attributes for a path %s", f), err) .printStackTrace(System.err); } // Path parsing in java.nio is stricter than in java.io. // Not returning here to give a chance to the original implementation. } catch (@SuppressWarnings("removal") IOException | AccessControlException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("Can't get attributes for a path %s", f), err) .printStackTrace(System.err); } return getSeparator() == '/' && f.getName().startsWith(".") ? BA_HIDDEN : 0; } } return parent.getBooleanAttributes(f); } @Override public boolean checkAccess(File f, int access) { boolean result = checkAccess0(f, access); if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.checkAccess(%s, %d) = %b%n", f, access, result); } return result; } private boolean checkAccess0(File f, int access) { @SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath()); if (nioFs != null) { if (f.getPath().isEmpty()) { // Both access() in Posix and GetFileAttributesW in Windows treat an empty path // as an invalid argument and return an error. return false; } try { Path path = nioFs.getPath(f.getPath()); int i = 0; AccessMode[] accessMode = new AccessMode[3]; if ((access & ACCESS_READ) != 0) { accessMode[i++] = AccessMode.READ; } if ((access & ACCESS_WRITE) != 0) { accessMode[i++] = AccessMode.WRITE; } if ((access & ACCESS_EXECUTE) != 0) { accessMode[i++] = AccessMode.EXECUTE; } accessMode = Arrays.copyOf(accessMode, i); nioFs.provider().checkAccess(path, accessMode); return true; } catch (@SuppressWarnings("removal") IOException | AccessControlException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("Can't check access for a path %s", f), err) .printStackTrace(System.err); } return false; } catch (InvalidPathException err) { throw new InternalError(err.getMessage(), err); } } return parent.checkAccess(f, access); } @Override public boolean setPermission(File f, int access, boolean enable, boolean owneronly) { boolean result = setPermission0(f, access, enable, owneronly); if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.setPermission(%s, %d, %b, %b) = %b%n", f, access, enable, owneronly, result); } return result; } private boolean setPermission0(File f, int access, boolean enable, boolean owneronly) { java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath()); if (nioFs != null) { return setPermission0(nioFs, f, access, enable, owneronly); } return parent.setPermission(f, access, enable, owneronly); } @Override public long getLastModifiedTime(File f) { try { long result = getLastModifiedTime0(f); if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.getLastModifiedTime(%s) = %d%n", f, result); } return result; } catch (RuntimeException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("IoOverNioFileSystem.getLastModifiedTime(%s) threw an error", f), err) .printStackTrace(System.err); } throw err; } } private long getLastModifiedTime0(File f) { @SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath()); if (nioFs != null) { try { Path path = nioFs.getPath(f.getPath()); nioFs.provider().readAttributes(path, BasicFileAttributes.class).lastModifiedTime(); } catch (IOException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("Can't get last modified time for a path %s", f), err) .printStackTrace(System.err); } return 0; } catch (InvalidPathException err) { throw new InternalError(err.getMessage(), err); } } return parent.getLastModifiedTime(f); } @Override public long getLength(File f) { try { long result = getLength0(f); if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.getLength(%s) = %d%n", f, result); } return result; } catch (RuntimeException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("IoOverNioFileSystem.getLength(%s) threw an error%n", f), err) .printStackTrace(System.err); } throw err; } } private long getLength0(File f) { @SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath()); try { if (nioFs != null) { Path path = nioFs.getPath(f.getPath()); return nioFs.provider().readAttributes(path, BasicFileAttributes.class).size(); } } catch (IOException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("Can't get file length for a path %s", f), err) .printStackTrace(System.err); } return 0; } catch (InvalidPathException err) { throw new InternalError(err.getMessage(), err); } return parent.getLength(f); } @Override public boolean createFileExclusively(String pathname) throws IOException { try { boolean result = createFileExclusively0(pathname); if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.createFileExclusively(%s) = %b%n", pathname, result); } return result; } catch (IOException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("IoOverNioFileSystem.createFileExclusively(%s) threw an error", pathname), err) .printStackTrace(System.err); } throw err; } } private boolean createFileExclusively0(String pathname) throws IOException { java.nio.file.FileSystem nioFs = acquireNioFs(pathname); try { // In case of an empty pathname, the default file system always throws an exception. if (pathname != null && !pathname.isEmpty() && nioFs != null) { Path path = nioFs.getPath(pathname); nioFs.provider().newByteChannel( path, EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE) ).close(); return true; } } catch (AccessDeniedException err) { // A special case for Windows. It can happen if it is an already existing directory. if (Files.exists(nioFs.getPath(pathname))) { return false; } } catch (FileAlreadyExistsException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("Can't exclusively create a file %s", pathname), err) .printStackTrace(System.err); } return false; } catch (InvalidPathException err) { throw new IOException(err.getMessage(), err); // The default file system would throw IOException too. } catch (IOException err) { String originalMessage = IoToNioErrorMessageHolder.removeMessage(err); if (originalMessage != null) { throw new IOException(originalMessage, err); } else { throw err; } } return parent.createFileExclusively(pathname); } @Override public boolean delete(File f) { try { boolean result = delete0(f, true); if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.delete(%s) = %b%n", f, result); } return result; } catch (RuntimeException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("IoOverNioFileSystem.delete(%s) threw an error", f), err) .printStackTrace(System.err); } throw err; } } private boolean delete0(File f, boolean mayRepeat) { @SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath()); if (nioFs != null) { Path path = null; try { path = nioFs.getPath(f.getPath()); nioFs.provider().delete(path); return true; } catch (InvalidPathException err) { throw new InternalError(err.getMessage(), err); } catch (IOException err) { if (err instanceof AccessDeniedException && mayRepeat && path != null && nioFs.supportedFileAttributeViews().contains("dos")) { try { DosFileAttributeView attrs = nioFs.provider() .getFileAttributeView(path, DosFileAttributeView.class, LinkOption.NOFOLLOW_LINKS); if (attrs != null) { attrs.setReadOnly(false); return delete0(f, false); } } catch (IOException ignored) { // Nothing. } } if (DEBUG.writeErrors()) { new Throwable(String.format("Can't delete a path %s", f), err) .printStackTrace(System.err); } return false; } } return parent.delete(f); } @Override public String[] list(File f) { try { String[] result = list0(f); if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.list(%s) = %s%n", f, Arrays.toString(result)); } return result; } catch (RuntimeException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("IoOverNioFileSystem.list(%s) threw an error", f), err) .printStackTrace(System.err); } throw err; } } private String[] list0(File f) { @SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath()); if (nioFs != null) { String pathStr = f.getPath(); if (pathStr.isEmpty()) { // Java_java_io_UnixFileSystem_list0 calls opendir(), and it returns an error for an empty path. // Java_java_io_WinNTFileSystem_list0 returns null // after calling fileToNTPath and before any WinAPI call. return null; } try { if (getSeparator() == '\\') { // Java_java_io_WinNTFileSystem_list0 deliberately and explicitly removes trailing spaces from the path. // It doesn't happen in Java_java_io_UnixFileSystem_list0 int i = pathStr.length(); while (i > 0 && pathStr.charAt(i - 1) == ' ') { i--; } pathStr = pathStr.substring(0, i); } Path path = nioFs.getPath(pathStr); try (DirectoryStream children = nioFs.provider().newDirectoryStream(path, AcceptAllFilter.FILTER)) { List result = new ArrayList<>(); for (Path child : children) { result.add(child.getFileName().toString()); } return result.toArray(String[]::new); } } catch (InvalidPathException err) { throw new InternalError(err.getMessage(), err); } catch (IOException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("Can't list a path %s", f), err) .printStackTrace(System.err); } return null; } } return parent.list(f); } @Override public boolean createDirectory(File f) { try { boolean result = createDirectory0(f); if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.createDirectory(%s) = %b%n", f, result); } return result; } catch (RuntimeException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("IoOverNioFileSystem.createDirectory(%s) threw an error", f), err) .printStackTrace(System.err); } throw err; } } private boolean createDirectory0(File f) { @SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath()); if (nioFs != null) { try { Path path = nioFs.getPath(f.getPath()); nioFs.provider().createDirectory(path); return true; } catch (InvalidPathException err) { throw new InternalError(err.getMessage(), err); } catch (IOException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("Can't create a directory %s", f), err) .printStackTrace(System.err); } return false; } } return parent.createDirectory(f); } @Override public boolean rename(File f1, File f2) { try { boolean result = rename0(f1, f2); if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.rename(%s, %s) = %b%n", f1, f2, result); } return result; } catch (RuntimeException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("IoOverNioFileSystem.rename(%s, %s) threw an error", f1, f2), err) .printStackTrace(System.err); } throw err; } } private boolean rename0(File f1, File f2) { @SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(null); if (nioFs != null) { try { Path path1 = nioFs.getPath(f1.getPath()); Path path2 = nioFs.getPath(f2.getPath()); nioFs.provider().move(path1, path2, StandardCopyOption.REPLACE_EXISTING); return true; } catch (InvalidPathException err) { throw new InternalError(err.getMessage(), err); } catch (IOException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("Can't rename %s to %s", f1, f2), err) .printStackTrace(System.err); } return false; } } return parent.rename(f1, f2); } @Override public boolean setLastModifiedTime(File f, long time) { try { boolean result = setLastModifiedTime0(f, time); if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.setLastModifiedTime(%s, %d) = %b%n", f, time, result); } return result; } catch (RuntimeException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("IoOverNioFileSystem.setLastModifiedTime(%s, %d) threw an error", f, time), err) .printStackTrace(System.err); } throw err; } } private boolean setLastModifiedTime0(File f, long time) { @SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath()); if (nioFs != null) { if (f.getPath().isEmpty()) { // Here happens the same as in checkAccess0. return false; } try { Path path = nioFs.getPath(f.getPath()); var view = nioFs.provider().getFileAttributeView(path, BasicFileAttributeView.class); if (view != null) { view.setTimes(FileTime.fromMillis(time), null, null); return true; } else { return false; } } catch (InvalidPathException err) { throw new InternalError(err.getMessage(), err); } catch (IOException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("Can't set last modified time of %s", f), err) .printStackTrace(System.err); } return false; } } return parent.setLastModifiedTime(f, time); } @Override public boolean setReadOnly(File f) { try { java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath()); boolean result; if (nioFs != null) { result = setPermission0(nioFs, f, ACCESS_WRITE, false, false); } else { result = parent.setReadOnly(f); } if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.setReadOnly(%s) = %b%n", f, result); } return result; } catch (RuntimeException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("IoOverNioFileSystem.setReadOnly(%s) threw an error", f), err) .printStackTrace(System.err); } throw err; } } @Override public File[] listRoots() { try { File[] result = listRoots0(); if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.listRoots() = %s%n", Arrays.toString(result)); } return result; } catch (RuntimeException err) { if (DEBUG.writeErrors()) { new Throwable("IoOverNioFileSystem.listRoots() threw an error", err) .printStackTrace(System.err); } throw err; } } private File[] listRoots0() { @SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(null); if (nioFs != null) { List roots = new ArrayList<>(); for (Path rootDirectory : nioFs.getRootDirectories()) { roots.add(rootDirectory.toFile()); } return roots.toArray(File[]::new); } return parent.listRoots(); } @Override public long getSpace(File f, int t) { try { long result = getSpace0(f, t); if (DEBUG.writeTraces()) { System.err.printf("IoOverNioFileSystem.getSpace(%s, %d) = %d%n", f, t, result); } return result; } catch (RuntimeException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("IoOverNioFileSystem.getSpace(%s, %d) threw an error", f, t), err) .printStackTrace(System.err); } throw err; } } private long getSpace0(File f, int t) { @SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath()); if (nioFs != null) { try { Path path = nioFs.getPath(f.getPath()); FileStore store = nioFs.provider().getFileStore(path); return switch (t) { case SPACE_TOTAL -> store.getTotalSpace(); case SPACE_USABLE -> store.getUsableSpace(); case SPACE_FREE -> store.getUnallocatedSpace(); default -> throw new IllegalArgumentException("Invalid space type: " + t); }; } catch (InvalidPathException err) { throw new InternalError(err.getMessage(), err); } catch (IOException err) { if (DEBUG.writeErrors()) { new Throwable(String.format("Can't get space %s for a path %s", t, f), err) .printStackTrace(System.err); } return 0; } } return parent.getSpace(f, t); } @Override public int getNameMax(String path) { // Seems impossible with java.nio. return parent.getNameMax(path); } @Override public int compare(File f1, File f2) { @SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(null); if (nioFs != null) { try { Path p1 = nioFs.getPath(f1.getPath()); Path p2 = nioFs.getPath(f2.getPath()); return p1.compareTo(p2); } catch (InvalidPathException ignored) { // Path parsing in java.nio is stricter than in java.io. // Giving a chance to the original implementation. } } return parent.compare(f1, f2); } @Override public int hashCode(File f) { return parent.hashCode(f); } private static class AcceptAllFilter implements DirectoryStream.Filter { static final AcceptAllFilter FILTER = new AcceptAllFilter(); private AcceptAllFilter() { } @Override public boolean accept(Path entry) { return true; } } }