mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-06 09:29:38 +01:00
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 commit82693aa985) (cherry picked from commit732a5c9ad6)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
11
src/java.base/windows/classes/sun/nio/fs/WindowsNativeDispatcher.java
Normal file → Executable file
11
src/java.base/windows/classes/sun/nio/fs/WindowsNativeDispatcher.java
Normal file → Executable 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 {
|
||||
|
||||
53
src/java.base/windows/native/libnio/fs/WindowsNativeDispatcher.c
Normal file → Executable file
53
src/java.base/windows/native/libnio/fs/WindowsNativeDispatcher.c
Normal file → Executable 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
|
||||
|
||||
48
test/jdk/java/nio/file/Files/GDriveTest.java
Normal file
48
test/jdk/java/nio/file/Files/GDriveTest.java
Normal 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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user