JDK-8034863 : java.nio.io.Files.isWritable fails on drives with a subst drive letter
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.nio
  • Affected Version: 7u51
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_7
  • CPU: x86_64
  • Submitted: 2014-02-08
  • Updated: 2014-11-14
  • Resolved: 2014-02-13
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
java version "1.7.0_51"
Java(TM) SE Runtime Environment (build 1.7.0_51-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.51-b03, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]

A DESCRIPTION OF THE PROBLEM :
Calling java.nio.io.Files.isWritable on a directory which resides on a drive that was created with subst.exe results in a boolean false answer, although the directory is perfectly writable.

My analysis shows, that the code calls the windows function GetVolumePathName for the directory on the subst drive an assumes, that the answer is the assigned drive letter. This seems not to be the case on a subst drive.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Create Directory c:\temp\temp
Create subst drive y: "subst y: c:\temp"

Files.isWritable("c:/temp/temp") returns true => OK
Files.isWritable("y:/temp") returns false => FAIL



EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Files.isWritable("y:/temp") should return true, because the directory is the same directory as c:/temp/temp which is writable.
ACTUAL -
Files.isWritable("y:/temp") returns false => FAIL

Analysis: 
Inner calls to GetVolumePathName for "y:/temp" do not yield "y:/" which is assumed by the following call to GetVolumeInfo.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
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);
		}
	}

}

---------- END SOURCE ----------