Files.isWritable and SUBST drives on Windows 7
David Polock
polock at gmx.de
Sun Feb 9 12:49:46 PST 2014
Hello,
I just stumbled upon an issue with the java.nio.file.Files.isWritable()
call when the questioned directory is on a drive which was created by
subst.exe.
I tried to use the TrueZIP 7 Library and Christian Schlichtherle
suggested to me, that I should share my experience on this mailing list.
So, here we go ;-)
In the short setup:
Assuming, there is a writable directory c:\temp\temp and we substitute
the first part of this path with a Drive letter y: (by calling subst y:
c:\temp) the directory y:\temp should be writable, because it is the
same as the first mentioned c:\temp\temp.
But the call to Files.isWritable(new File("y:/temp").toPath()) delivers
false.
Why? Digging in the code shows, that Files.isWritable leads to two calls
in WindowsFileStore.
First create(WindowsPath file) tries to determine the root of the
filesystem. The call
root = GetVolumePathName(target);
for a target on a physical drive (e.g c:\temp\temp) leads to the drive
letter with a \ (c:\).
For a substituted drive, the function calculates a directory (y:\temp).
This root is then used to call "GetVolumeInformation", which fails for
the directory name. The windows exception is caught and the result of
the call sequence is "not writable".
I don't know whether Windows 7 is buggy, or the call sequence is not
allowed, but the effect is a writable directory, that is reported as
read-only.
Best regards,
David
Example source code:
package bugreport.java;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
public class JavaNioOnSubstDrive {
public static void main(String[] args) {
/*
* Prc:
* Let c:\\ be a physical disk on a windows 7 system
* Let c:\\temp\\temp\\ be an accessible path
* Let y: be a substituted drive letter for c:\\temp - created
with "subst y: c:\temp"
* Let c:\\temp\\temp\\test.txt (aka y:\\temp\\test.txt) be a
writable file
*/
String dirOnDrive = "c:/temp/temp";
System.out
.println(Files.isWritable(Paths.get(dirOnDrive)) ? "Dir
on drive is writable"
: "Dir on drive is not writable");
String dirOnSubst = "y:/temp";
System.out
.println(Files.isWritable(Paths.get(dirOnSubst)) ? "Dir
on subst is writable"
: "Dir on subst is not writable");
/*
* Output: Dir on subst is not writable although we can
perfectly write
* to this directory:
*/
String fileOnDrive = dirOnDrive + "/test.txt";
String fileOnSubst = dirOnSubst + "/test.txt";
/*
* Verification:
*
* First let's check, if this is truc: Just write with plain
old File to
* the unwritable directory and read from the original file to
prove,
* that both files are the same file.
*/
try {
byte[] tempOut = new byte[256];
for (int i = 0; i < tempOut.length; i++) {
tempOut[i] = (byte) (Math.random() * 255);
}
FileOutputStream substOut = new FileOutputStream(fileOnSubst);
substOut.write(tempOut);
substOut.close();
byte[] tempIn = new byte[256];
FileInputStream driveInput = new FileInputStream(fileOnDrive);
driveInput.read(tempIn);
driveInput.close();
for (int i = 0; i < tempOut.length; i++) {
if (tempOut[i] != tempIn[i]) {
throw new IOException("Bytes differ");
}
}
} catch (IOException io) {
System.err.println("Verification of testsetup failed" + io);
}
/*
* Verfication does not report any problem!
*/
/*
* So let's dig in the depth: The problem is imho in the calls to
* "GetVolumePathName" and "GetVolumeInformation" for
retrieving the
* information, whether the volume contains a readonly filesystem.
*
* Access to sun.nio.fs.WindowsNativeDispatcher via reflection
methods.
*/
String volumePathNameOnDrive = GetVolumePathName(dirOnDrive);
System.out.println("Volume path name on drive: "
+ volumePathNameOnDrive);
// => c:/
String volumePathNameOnSubst = GetVolumePathName(dirOnSubst);
System.out.println("Volume path name on subst: "
+ volumePathNameOnSubst);
// => y:/temp/ !?!?!?
/*
* The call GetVolumeInformation works only with a drive letter
*/
Object volumeInfoForDrive =
GetVolumeInformation(volumePathNameOnDrive);
System.out.println("Volume info on drive: " + volumeInfoForDrive);
// Ok
Object volumeInfoForSubstDriveLetter = GetVolumeInformation("y:/");
System.out.println("Volume info on subst drive letter: "
+ volumeInfoForSubstDriveLetter);
// would be Ok
/*
* But this call fails, because GetVolumePathName did not deliver a
* valid volume name
*/
Object volumeInfoForSubst =
GetVolumeInformation(volumePathNameOnSubst);
System.out.println("Volume info on subst: " + volumeInfoForSubst);
// exceptions
}
private static String GetVolumePathName(String path) {
try {
Class cl = Class.forName("sun.nio.fs.WindowsNativeDispatcher");
Method methodGetVolumePathName = cl.getDeclaredMethod(
"GetVolumePathName", String.class);
methodGetVolumePathName.setAccessible(true);
Object result = methodGetVolumePathName.invoke(null, path);
return (String) result;
} catch (Throwable e1) {
throw new RuntimeException(e1);
}
}
private static Object GetVolumeInformation(String path) {
try {
Class cl = Class.forName("sun.nio.fs.WindowsNativeDispatcher");
Method methodGetVolumeInformation = cl.getDeclaredMethod(
"GetVolumeInformation", String.class);
methodGetVolumeInformation.setAccessible(true);
Object result = methodGetVolumeInformation.invoke(null, path);
return result;
} catch (Throwable e1) {
throw new RuntimeException(e1);
}
}
}
More information about the nio-discuss
mailing list