JBR-4118 NIO methods fail on Google Drive's virtual volume

If NtQueryDirectoryFile() failed with STATUS_INVALID_PARAMETER,
try again asking for less information with the FileDirectoryInformation
option as "information class". This option works on a mounted
Google Drive, but it doesn't provide file ids, which speed
up file listing. So it is used only as a fall-back solution.

(cherry picked from commit 82693aa985)
(cherry picked from commit 732a5c9ad6)
This commit is contained in:
Maxim Kartashev
2022-01-17 18:02:25 +03:00
committed by jbrbot
parent 37edb81662
commit b5ac25c83f
5 changed files with 192 additions and 42 deletions

View File

@@ -175,15 +175,17 @@ class WindowsDirectoryStream
nextOffset = 0;
}
long fullDirInformationAddress = queryDirectoryInformationBuffer.address() + nextOffset;
int nextEntryOffset = WindowsFileAttributes.getNextOffsetFromFileIdFullDirInformation(fullDirInformationAddress);
long dirInformationAddress = queryDirectoryInformationBuffer.address() + nextOffset;
int nextEntryOffset = WindowsFileAttributes.getNextOffsetFromFileDirInformation(
queryDirectoryInformation, dirInformationAddress);
nextOffset = nextEntryOffset == 0 ? -1 : nextOffset + nextEntryOffset;
name = WindowsFileAttributes.getFileNameFromFileIdFullDirInformation(fullDirInformationAddress);
name = WindowsFileAttributes.getFileNameFromFileDirInformation(
queryDirectoryInformation, dirInformationAddress);
if (isSelfOrParent(name)) {
// Skip "." and ".."
continue;
}
attrs = WindowsFileAttributes.fromFileIdFullDirInformation(fullDirInformationAddress, queryDirectoryInformation.volSerialNumber());
attrs = WindowsFileAttributes.fromFileDirInformation(queryDirectoryInformation, dirInformationAddress);
}
// return entry if accepted by filter

View File

@@ -140,6 +140,30 @@ class WindowsFileAttributes
private static final int OFFSETOF_FULL_DIR_INFO_FILE_ID = 72;
private static final int OFFSETOF_FULL_DIR_INFO_FILENAME = 80;
/**
* struct _FILE_DIRECTORY_INFORMATION {
* ULONG NextEntryOffset; // offset = 0
* ULONG FileIndex; // offset = 4
* LARGE_INTEGER CreationTime; // offset = 8
* LARGE_INTEGER LastAccessTime; // offset = 16
* LARGE_INTEGER LastWriteTime; // offset = 24
* LARGE_INTEGER ChangeTime; // offset = 32
* LARGE_INTEGER EndOfFile; // offset = 40
* LARGE_INTEGER AllocationSize; // offset = 48
* ULONG FileAttributes; // offset = 56
* ULONG FileNameLength; // offset = 60
* WCHAR FileName[1]; // offset = 64
* }
*/
private static final int OFFSETOF_DIR_INFO_NEXT_ENTRY_OFFSET = 0;
private static final int OFFSETOF_DIR_INFO_CREATION_TIME = 8;
private static final int OFFSETOF_DIR_INFO_LAST_ACCESS_TIME = 16;
private static final int OFFSETOF_DIR_INFO_LAST_WRITE_TIME = 24;
private static final int OFFSETOF_DIR_INFO_END_OF_FILE = 40;
private static final int OFFSETOF_DIR_INFO_FILE_ATTRIBUTES = 56;
private static final int OFFSETOF_DIR_INFO_FILENAME_LENGTH = 60;
private static final int OFFSETOF_DIR_INFO_FILENAME = 64;
// used to adjust values between Windows and java epoch
private static final long WINDOWS_EPOCH_IN_MICROSECONDS = -11644473600000000L;
@@ -292,43 +316,79 @@ class WindowsFileAttributes
}
/**
* Create a WindowsFileAttributes from a FILE_ID_FULL_DIR_INFORMATION structure
* Create a WindowsFileAttributes from either a FILE_ID_FULL_DIR_INFORMATION
* or FILE_DIRECTORY_INFORMATION structure depending on the value of
* QueryDirectoryInformation.supportsFullIdInfo().
*/
static WindowsFileAttributes fromFileIdFullDirInformation(long address, int volSerialNumber) {
int fileAttrs = unsafe.getInt(address + OFFSETOF_FULL_DIR_INFO_FILE_ATTRIBUTES);
long creationTime = unsafe.getLong(address + OFFSETOF_FULL_DIR_INFO_CREATION_TIME);
long lastAccessTime = unsafe.getLong(address + OFFSETOF_FULL_DIR_INFO_LAST_ACCESS_TIME);
long lastWriteTime = unsafe.getLong(address + OFFSETOF_FULL_DIR_INFO_LAST_WRITE_TIME);
long size = unsafe.getLong(address + OFFSETOF_FULL_DIR_INFO_END_OF_FILE);
int reparseTag = isReparsePoint(fileAttrs) ?
unsafe.getInt(address + OFFSETOF_FULL_DIR_INFO_EA_SIZE) : 0;
int fileIndexLow = unsafe.getInt(address + OFFSETOF_FULL_DIR_INFO_FILE_ID);
int fileIndexHigh = unsafe.getInt(address + OFFSETOF_FULL_DIR_INFO_FILE_ID + 4);
static WindowsFileAttributes fromFileDirInformation(QueryDirectoryInformation info, long address) {
if (info.supportsFullIdInfo()) { // address points to struct FILE_ID_FULL_DIR_INFORMATION
int fileAttrs = unsafe.getInt(address + OFFSETOF_FULL_DIR_INFO_FILE_ATTRIBUTES);
long creationTime = unsafe.getLong(address + OFFSETOF_FULL_DIR_INFO_CREATION_TIME);
long lastAccessTime = unsafe.getLong(address + OFFSETOF_FULL_DIR_INFO_LAST_ACCESS_TIME);
long lastWriteTime = unsafe.getLong(address + OFFSETOF_FULL_DIR_INFO_LAST_WRITE_TIME);
long size = unsafe.getLong(address + OFFSETOF_FULL_DIR_INFO_END_OF_FILE);
int reparseTag = isReparsePoint(fileAttrs) ?
unsafe.getInt(address + OFFSETOF_FULL_DIR_INFO_EA_SIZE) : 0;
int volSerialNumber = info.volSerialNumber();
int fileIndexLow = unsafe.getInt(address + OFFSETOF_FULL_DIR_INFO_FILE_ID);
int fileIndexHigh = unsafe.getInt(address + OFFSETOF_FULL_DIR_INFO_FILE_ID + 4);
return new WindowsFileAttributes(fileAttrs,
creationTime,
lastAccessTime,
lastWriteTime,
size,
reparseTag,
volSerialNumber,
fileIndexHigh, // fileIndexHigh
fileIndexLow); // fileIndexLow
return new WindowsFileAttributes(fileAttrs,
creationTime,
lastAccessTime,
lastWriteTime,
size,
reparseTag,
volSerialNumber,
fileIndexHigh, // fileIndexHigh
fileIndexLow); // fileIndexLow
} else { // address points to FILE_DIRECTORY_INFORMATION
int fileAttrs = unsafe.getInt(address + OFFSETOF_DIR_INFO_FILE_ATTRIBUTES);
long creationTime = unsafe.getLong(address + OFFSETOF_DIR_INFO_CREATION_TIME);
long lastAccessTime = unsafe.getLong(address + OFFSETOF_DIR_INFO_LAST_ACCESS_TIME);
long lastWriteTime = unsafe.getLong(address + OFFSETOF_DIR_INFO_LAST_WRITE_TIME);
long size = unsafe.getLong(address + OFFSETOF_DIR_INFO_END_OF_FILE);
int reparseTag = 0;
// Don't provide the real serial number as the reset of the code assumes that presence of
// the volume serial means that the file id is also valid, which isn't the case here.
// This will make comparing Path's for equality slower as the file id serves as
// a unique file key (@see fileKey()).
int volSerialNumber = 0;
int fileIndexLow = 0;
int fileIndexHigh = 0;
return new WindowsFileAttributes(fileAttrs,
creationTime,
lastAccessTime,
lastWriteTime,
size,
reparseTag,
volSerialNumber,
fileIndexHigh,
fileIndexLow);
}
}
static int getNextOffsetFromFileIdFullDirInformation(long address) {
return unsafe.getInt(address + OFFSETOF_FULL_DIR_INFO_NEXT_ENTRY_OFFSET);
static int getNextOffsetFromFileDirInformation(QueryDirectoryInformation info, long address) {
return unsafe.getInt(address
+ (info.supportsFullIdInfo() ? OFFSETOF_FULL_DIR_INFO_NEXT_ENTRY_OFFSET
: OFFSETOF_DIR_INFO_NEXT_ENTRY_OFFSET));
}
static String getFileNameFromFileIdFullDirInformation(long address) {
static String getFileNameFromFileDirInformation(QueryDirectoryInformation info, long address) {
// copy the name
int nameLengthInBytes = unsafe.getInt(address + OFFSETOF_FULL_DIR_INFO_FILENAME_LENGTH);
int nameLengthInBytes = unsafe.getInt(address
+ (info.supportsFullIdInfo() ? OFFSETOF_FULL_DIR_INFO_FILENAME_LENGTH
: OFFSETOF_DIR_INFO_FILENAME_LENGTH));
if ((nameLengthInBytes % 2) != 0) {
throw new AssertionError("FileNameLength is not a multiple of 2");
}
char[] nameAsArray = new char[nameLengthInBytes/2];
unsafe.copyMemory(null, address + OFFSETOF_FULL_DIR_INFO_FILENAME, nameAsArray,
Unsafe.ARRAY_CHAR_BASE_OFFSET, nameLengthInBytes);
unsafe.copyMemory(null,
address + (info.supportsFullIdInfo() ? OFFSETOF_FULL_DIR_INFO_FILENAME
: OFFSETOF_DIR_INFO_FILENAME),
nameAsArray,
Unsafe.ARRAY_CHAR_BASE_OFFSET, nameLengthInBytes);
return new String(nameAsArray);
}

View File

@@ -285,10 +285,17 @@ class WindowsNativeDispatcher {
static class QueryDirectoryInformation {
private long handle;
private int volSerialNumber;
/**
* Set by OpenNtQueryDirectoryInformation0() to
* true if dealing with struct FILE_ID_FULL_DIR_INFORMATION
* and to false if it's struct FILE_DIRECTORY_INFORMATION.
*/
private boolean supportsFullIdInfo;
private QueryDirectoryInformation() { }
public long handle() { return handle; }
public int volSerialNumber() { return volSerialNumber; }
public boolean supportsFullIdInfo() { return supportsFullIdInfo; }
}
private static native void OpenNtQueryDirectoryInformation0(long lpFileName, long buffer, int bufferSize, QueryDirectoryInformation obj)
throws WindowsException;
@@ -296,13 +303,13 @@ class WindowsNativeDispatcher {
static boolean NextNtQueryDirectoryInformation(QueryDirectoryInformation data, NativeBuffer buffer) throws WindowsException {
long comp = Blocker.begin();
try {
return NextNtQueryDirectoryInformation0(data.handle(), buffer.address(), buffer.size());
return NextNtQueryDirectoryInformation0(data.handle(), data.supportsFullIdInfo(), buffer.address(), buffer.size());
} finally {
Blocker.end(comp);
}
}
private static native boolean NextNtQueryDirectoryInformation0(long handle, long buffer, int bufferSize)
private static native boolean NextNtQueryDirectoryInformation0(long handle, boolean supportsFullIdInfo, long buffer, int bufferSize)
throws WindowsException;
static void CloseNtQueryDirectoryInformation(QueryDirectoryInformation data) throws WindowsException {

View File

@@ -54,6 +54,7 @@ static jfieldID findStream_name;
static jfieldID queryDirectoryInformation_handle;
static jfieldID queryDirectoryInformation_volSerialNumber;
static jfieldID queryDirectoryInformation_supportsFullIdInfo;
static jfieldID volumeInfo_fsName;
static jfieldID volumeInfo_volName;
@@ -121,8 +122,10 @@ Java_sun_nio_fs_WindowsNativeDispatcher_initIDs(JNIEnv* env, jclass this)
CHECK_NULL(clazz);
queryDirectoryInformation_handle = (*env)->GetFieldID(env, clazz, "handle", "J");
CHECK_NULL(queryDirectoryInformation_handle);
queryDirectoryInformation_volSerialNumber = (*env)->GetFieldID(env, clazz, "volSerialNumber", "I");;
queryDirectoryInformation_volSerialNumber = (*env)->GetFieldID(env, clazz, "volSerialNumber", "I");
CHECK_NULL(queryDirectoryInformation_volSerialNumber);
queryDirectoryInformation_supportsFullIdInfo = (*env)->GetFieldID(env, clazz, "supportsFullIdInfo", "Z");
CHECK_NULL(queryDirectoryInformation_supportsFullIdInfo);
clazz = (*env)->FindClass(env, "sun/nio/fs/WindowsNativeDispatcher$VolumeInformation");
CHECK_NULL(clazz);
@@ -482,6 +485,7 @@ Java_sun_nio_fs_WindowsNativeDispatcher_OpenNtQueryDirectoryInformation0(JNIEnv*
return;
}
jboolean supportsFullIdInfo = JNI_TRUE;
status = NtQueryDirectoryFile_func(
handle, // FileHandle
NULL, // Event
@@ -504,15 +508,42 @@ Java_sun_nio_fs_WindowsNativeDispatcher_OpenNtQueryDirectoryInformation0(JNIEnv*
*/
if (status == STATUS_INVALID_PARAMETER) {
DWORD attributes = GetFileAttributesW(lpFileName);
if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
status = STATUS_NOT_A_DIRECTORY;
const jboolean areAttributesValid = (attributes != INVALID_FILE_ATTRIBUTES);
const jboolean isDirectory = areAttributesValid
&& (attributes & FILE_ATTRIBUTE_DIRECTORY);
if (!areAttributesValid || !isDirectory) {
if (areAttributesValid && !isDirectory) status = STATUS_NOT_A_DIRECTORY;
win32ErrorCode = RtlNtStatusToDosError_func(status);
throwWindowsException(env, win32ErrorCode);
CloseHandle(handle);
return;
}
}
win32ErrorCode = RtlNtStatusToDosError_func(status);
throwWindowsException(env, win32ErrorCode);
CloseHandle(handle);
return;
/* If it's a directory, we can have another go by asking for
* less information with the FileDirectoryInformation
* information class. This works on a mounted Google Drive, for instance.
*/
status = NtQueryDirectoryFile_func(
handle, // FileHandle
NULL, // Event
NULL, // ApcRoutine
NULL, // ApcContext
&ioStatusBlock, // IoStatusBlock
jlong_to_ptr(bufferAddress), // FileInformation
bufferSize, // Length
FileDirectoryInformation, // FileInformationClass
FALSE, // ReturnSingleEntry
NULL, // FileName
FALSE); // RestartScan
if (!NT_SUCCESS(status)) {
win32ErrorCode = RtlNtStatusToDosError_func(status);
throwWindowsException(env, win32ErrorCode);
CloseHandle(handle);
return;
}
supportsFullIdInfo = JNI_FALSE;
}
}
// This call allows retrieving the volume ID of this directory (and all its entries)
@@ -525,11 +556,12 @@ Java_sun_nio_fs_WindowsNativeDispatcher_OpenNtQueryDirectoryInformation0(JNIEnv*
(*env)->SetLongField(env, obj, queryDirectoryInformation_handle, ptr_to_jlong(handle));
(*env)->SetIntField(env, obj, queryDirectoryInformation_volSerialNumber, info.dwVolumeSerialNumber);
(*env)->SetBooleanField(env, obj, queryDirectoryInformation_supportsFullIdInfo, supportsFullIdInfo);
}
JNIEXPORT jboolean JNICALL
Java_sun_nio_fs_WindowsNativeDispatcher_NextNtQueryDirectoryInformation0(JNIEnv* env, jclass this,
jlong handle, jlong address, jint size)
jlong handle, jboolean supportsFullIdInfo, jlong address, jint size)
{
HANDLE h = (HANDLE)jlong_to_ptr(handle);
ULONG win32ErrorCode;
@@ -549,7 +581,8 @@ Java_sun_nio_fs_WindowsNativeDispatcher_NextNtQueryDirectoryInformation0(JNIEnv*
&ioStatusBlock, // IoStatusBlock
jlong_to_ptr(address), // FileInformation
size, // Length
FileIdFullDirectoryInformation, // FileInformationClass
supportsFullIdInfo ? FileIdFullDirectoryInformation
: FileDirectoryInformation, // FileInformationClass
FALSE, // ReturnSingleEntry
NULL, // FileName
FALSE); // RestartScan

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2021 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.
*
* 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.
*/
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/*
* @test
*
* @requires (os.family == "windows")
*
* @summary Verifies that listing a mounted Google Drive doesn't throw.
*
* @run main GDriveTest
*/
public class GDriveTest {
public static void main(String[] args) throws Exception {
final Path gdrivePath = Path.of("G:\\");
if (Files.exists(gdrivePath)) {
try (Stream<Path> stream = Files.list(gdrivePath)) {
System.out.println(stream.collect(Collectors.toList()));
}
}
}
}