JDK-8232092 : (fs) Files::isWritable returns false on a writeable root directory (win)
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.nio
  • Affected Version: 13
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_10
  • CPU: x86
  • Submitted: 2019-10-10
  • Updated: 2020-10-15
  • Resolved: 2020-10-07
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 16
16 b20Fixed
Related Reports
Relates :  
Description
jdeps uses `Files.isWritable(Path)` in `JdepsTask.java` to validate a user-given target path for generated dot and/or module-info.java files.

That `Files.isWritable(Path)` method returns `false` on Windows, although the given path represents a directory the current user has write access to.

Here's an example for the default "C:\" root directory:

jshell> Path.of("C:\\").toFile().canWrite()
$10 ==> true
jshell> Files.isWritable(Path.of("C:\\"))
$11 ==> false

Here are the two usages of `Files.isWritable(Path)`:
- https://github.com/openjdk/jdk/blob/7256d38458190c2e538b1082dcaca575f2dd5d6d/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java#L614
- https://github.com/openjdk/jdk/blob/7256d38458190c2e538b1082dcaca575f2dd5d6d/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java#L621

Workaround:
Provide a non-existing directory to take the first exit of the two `if`-clauses linked above to by-pass the call of `Files.isWritable(Path)`.
Comments
Changeset: abe25937 Author: Nhat Nguyen <honguye@microsoft.com> Committer: Alan Bateman <alanb@openjdk.org> Date: 2020-10-07 19:45:20 +0000 URL: https://git.openjdk.java.net/jdk/commit/abe25937
07-10-2020

I tried another RAM Disk software on my box -- namely https://github.com/ArsenalRecon/Arsenal-Image-Mounter -- and as this tool relies on the Windows Volume Mount Manager APIs, it works like a charm with Files.isWritable().
09-07-2020

Here's the summay Nikola posted to core-libs-dev https://mail.openjdk.java.net/pipermail/core-libs-dev/2020-July/067594.html (which was moved by Brian Burkhalter to nio-dev and linked in this issue). I was asked to help look into JDK-8232092 by Christian Stein (the bug reporter), and since I don't have an OpenJDK account yet I'm sharing my findings on the issue here. I looked into two separate issues that might cause FileSystemProvider.checkAccess to return an incorrect result as reported. The first issue seems related to substituted directories with volumes, or virtual drives on Windows. The problem happens if the Windows GetVolumePathNameW API is called with a virtual drive letter in the path, causing the API to return ERROR_INVALID_PARAMETER or error code 87. The code in WindowsFileStore::create makes a call to WindowsLinkSupport::getFinalPath, however the getFinalPath method will avoid expanding the virtual drive path, by calling GetFinalPathNameByHandle, because the path isn't a symbolic link. The existing code in WindowsFileStore::create currently checks for the ErrorLevel of ERROR_DIR_NOT_ROOT, and it properly calls the path expansion API, however it doesn't check for ERROR_INVALID_PARAMETER which might happen if a path with virtual drive letter is used. We can follow the steps below to reproduce the issue: - Have a writeable directory on Windows, e.g. C:\Temp - Run "subst T: C:\Temp" on the command line to create the virtual drive T for C:\Temp - Check with "cacls T:" and "cacls C:\Temp" that you have the exact same access control permissions - Run "FileSystems.getDefault().provider().checkAccess(Path.of("T:\\"), java.nio.file.AccessMode.WRITE)" in jshell and you'll see an Exception java.nio.file.FileSystemException: T:\: The parameter is incorrect. Running the same command on C:\\Temp will return without an exception. I've attached at the bottom of this email a simple patch that address the virtual drive issue. There might be a better way to fix this issue so any feedback is appreciated. The second issue reported is using a virtual drive with the ImDisk utility to create a Virtual RAM Disk on Windows (Z: drive in the bug report). As mentioned in the bug report the failure is slightly different, in this case GetFinalPathNameByHandleW is called and the API returns ERROR_INVALID_FUNCTION. This error is caused by the third-party utility design/implementation. With ImDisk virtual disks, applications that rely on Windows Volume Mount Manager APIs, like GetFinalPathNameByHandleW, will fail because ImDisk does not interact with Windows Volume Mount Manager at all, it effectively bypasses it. Essentially, GetFinalPathNameByHandle and few other APIs will never work properly with ImDisk virtual drives. Cheers, Nikola Grcevski Microsoft diff --git a/src/java.base/windows/classes/sun/nio/fs/WindowsFileStore.java b/src/java.base/windows/classes/sun/nio/fs/WindowsFileStore.java index acbb2c15f2a..9b4b4117b24 100644 --- a/src/java.base/windows/classes/sun/nio/fs/WindowsFileStore.java +++ b/src/java.base/windows/classes/sun/nio/fs/WindowsFileStore.java @@ -79,10 +79,12 @@ class WindowsFileStore // volume that the link is on so we need to call it with the // final target String target = WindowsLinkSupport.getFinalPath(file, true); + try { return createFromPath(target); } catch (WindowsException e) { - if (e.lastError() != ERROR_DIR_NOT_ROOT) + if (e.lastError() != ERROR_DIR_NOT_ROOT && + e.lastError() != ERROR_INVALID_PARAMETER) throw e; target = WindowsLinkSupport.getFinalPath(file); if (target == null)
09-07-2020

Pinged Nikola. He'll take a look.
03-07-2020

If someone from Microsoft can explain why GetFinalPathNameByHandle returns ERROR_INVALID_FUNCTION then it would be very useful. We know how to workaround this in the JDK code but have more information on the errors from GetFinalPathNameByHandle would allow us to validate the workaround.
03-07-2020

> I'll try to give more groups/users "FULL ACCESS" and/or "WRITE ACCESS" and see what happens. To no avail at all. The same exception (ERROR_INVALID_FUNCTION) keeps coming up what- and who-ever I add to the ACL, even for a fresh directory I created with the current user. Shall we loop in folks from Microsoft? https://bugs.openjdk.java.net/browse/JDK-8234076 was resolved by Nikola Grcevski, for example.
03-07-2020

Thanks for this, this is very useful. Testing a file/directory to see if it writable requires three steps, one to test write access to the file, the second to test the DOS read-only attribute, and the third is test if the volume is read-only. It's the third part that is failing, looks like GetFinalPathNameByHandle is failing with ERROR_INVALID_FUNCTION. We need to research this a bit more - it may be that testing if the volume is read-only should be skipped for this case.
03-07-2020

Z: is using NTFS ... I'll try to give more groups/users "FULL ACCESS" and/or "WRITE ACCESS" and see what happens.
03-07-2020

jshell> FileSystems.getDefault().provider().checkAccess(Path.of("Z:\\Temp"), java.nio.file.AccessMode.WRITE) | Exception java.nio.file.FileSystemException: Z:\Temp: Unzul��ssige Funktion | at WindowsException.translateToIOException (WindowsException.java:92) | at WindowsException.rethrowAsIOException (WindowsException.java:103) | at WindowsException.rethrowAsIOException (WindowsException.java:108) | at WindowsLinkSupport.getFinalPath (WindowsLinkSupport.java:82) | at WindowsFileStore.create (WindowsFileStore.java:87) | at WindowsFileSystemProvider.checkAccess (WindowsFileSystemProvider.java:405) | at (#1:1) "invalid function" is the best-guess translation of the detailed error message. Passing AccessMode.READ and AccessMode.EXECUTE don't provoke the exception. I understand that falling back to File::canWrite is not an option here -- but if Files::write(path.resolve(file)) works for path Files::isWritable should return true.
03-07-2020

Can you also test this in shell: FileSystems.getDefault().provider().checkAccess(path, AccessMode.WRITE); I'm curious if you get an exception. Files.isWriteable may return false when it's not possible to determine if the file/directory is writable. Also just to point out that File::canWrite returns the value of the DOS readonly attribute, this has always been broken. (and thanks for the output from cacls.exe, the output hints that it might not support access control, you'll see what I mean if you run it on a file on a NFTS partition).
03-07-2020

Just tried this: jshell> Files.writeString(Path.of("Z://Temp/123.txt"), "123") $4 ==> Z:\Temp\123.txt jshell> Files.readString(Path.of("Z://Temp/123.txt")) $5 ==> "123" Works as expected. cacls Z:\Temp Z:\Temp Jeder:(OI)(CI)F I guess "Jeder" refers to the "Everybody" group ... and F means Full Access. Same same for "icacls.exe": icacls Z:\Temp Z:\Temp Jeder:(OI)(CI)(F)
03-07-2020

Here's another usage of "Files.isWritable()" that prevents a JVM to launch due to JFR checking the temporary directory for write access at startup: - https://github.com/openjdk/jdk/blob/4506975561aad87ae8c57f193710ba19ddfae58f/src/jdk.jfr/share/classes/jdk/jfr/internal/SecuritySupport.java#L398 The error message reads: Can't create Flight Recorder. JFR repository directory (Z:\Temp) exists, but isn't writable Error occurred during initialization of VM Failure when starting JFR on_vm_start Note: Drive letter Z: points to an "ImDisk Virtual Disk" which works for other processes (including hsperfdata recordings). Passing `-XX:StartFlightRecording:disk=false` does NOT prevent the error to occur. Here's a JShell (14.0.1) session showing similar results as shown in the initial issue description: jshell> Path.of("Z://Temp").toFile().canWrite() $1 ==> true jshell> Files.isWritable(Path.of("Z://Temp")) $2 ==> false Shall I create a new issue for this case?
03-07-2020

[~cstein] Can you use the cacls.exe tool to print out the access of Z:\Temp? It's very possible that the issue it is not possible to determinate (from DACL) whether the directory is writable or not.
03-07-2020

java.io.File::canWrite is completely broken on Windows because it only checks the DOS read-only attribute. This issue has been looked at several times and not clear that it can be fixed without creating a compatibility issue. Files.isWritable uses the file ACL so I think we need to see what the issue is with the root directory (C:\) to know if it is returning the correct answer or not (it may be that access to the root directory cannot be obtained, in which case isWritable is specified to return false).
06-12-2019

Moving to core-libs/java.io for evalation.
05-12-2019

Prepared a PR here: https://github.com/openjdk/jdk/pull/2
04-12-2019