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