Skip to content

Commit 3324fdd

Browse files
committed
Windows: Use NtQueryDirectoryFile to enumerate file entries
* NtQueryDirectoryFile is more efficient than FindFindFile/FindNextFile * Include a minimal copy of "ntifs.h" containing the definitions required to call NtQueryDirectoryFile and RtlNtStatusToDosError. * Use a new set of JNI entry point to maximize performance. Instead of c++ code calling back into Java, we use a DirectByteBuffer to share native memory between C++ and Java, making the JNI interface less chatty and more efficient. This is about 20% faster than using Java callbacks. * This is available on Windows Vista/Windows Server 2008 and later. * Add entry point to check if new API is supported
1 parent f3ec80d commit 3324fdd

File tree

6 files changed

+518
-15
lines changed

6 files changed

+518
-15
lines changed

build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@ model {
260260
} else if (targetPlatform.operatingSystem.windows) {
261261
if (name.contains("_min")) {
262262
cppCompiler.define "WINDOWS_MIN"
263+
} else {
264+
// For NtQueryDirectoryFile
265+
linker.args "ntdll.lib"
263266
}
264267
cppCompiler.args "-I${org.gradle.internal.jvm.Jvm.current().javaHome}/include"
265268
cppCompiler.args "-I${org.gradle.internal.jvm.Jvm.current().javaHome}/include/win32"

src/main/cpp/win.cpp

Lines changed: 154 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
#include <windows.h>
2222
#include <Shlwapi.h>
2323
#include <wchar.h>
24+
#ifndef WINDOWS_MIN
25+
#include "ntifs_min.h"
26+
#endif
2427

2528
#define ALL_COLORS (FOREGROUND_BLUE|FOREGROUND_RED|FOREGROUND_GREEN)
2629

@@ -31,6 +34,16 @@ void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result) {
3134
mark_failed_with_code(env, message, GetLastError(), NULL, result);
3235
}
3336

37+
#ifndef WINDOWS_MIN
38+
/*
39+
* Marks the given result as failed, using a NTSTATUS error code
40+
*/
41+
void mark_failed_with_ntstatus(JNIEnv *env, const char* message, NTSTATUS status, jobject result) {
42+
ULONG win32ErrorCode = RtlNtStatusToDosError(status);
43+
mark_failed_with_code(env, message, win32ErrorCode, NULL, result);
44+
}
45+
#endif
46+
3447
int map_error_code(int error_code) {
3548
if (error_code == ERROR_PATH_NOT_FOUND) {
3649
return FAILURE_NO_SUCH_FILE;
@@ -81,6 +94,7 @@ bool is_path_absolute_unc(wchar_t* path, int path_len) {
8194

8295
//
8396
// Returns a UTF-16 string that is the concatenation of |prefix| and |path|.
97+
// The string must be deallocated with a call to free().
8498
//
8599
wchar_t* add_prefix(wchar_t* path, int path_len, wchar_t* prefix) {
86100
int prefix_len = wcslen(prefix);
@@ -156,14 +170,13 @@ jlong lastModifiedNanos(FILETIME* time) {
156170
return ((jlong)time->dwHighDateTime << 32) | time->dwLowDateTime;
157171
}
158172

159-
jlong lastModifiedNanos(LARGE_INTEGER* time) {
160-
return ((jlong)time->HighPart << 32) | time->LowPart;
161-
}
162-
173+
//
174+
// Data structure holding information about a single file
175+
//
163176
typedef struct file_stat {
164-
int fileType;
165-
LONG64 lastModified;
166-
LONG64 size;
177+
LONG fileType;
178+
LONGLONG lastModified;
179+
LONGLONG size;
167180
} file_stat_t;
168181

169182
//
@@ -601,6 +614,140 @@ Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_readdir(JNIEn
601614
FindClose(dirHandle);
602615
}
603616

617+
//
618+
// Returns "true" is the various fastReaddirXxx calls are supported on this platform.
619+
//
620+
JNIEXPORT jboolean JNICALL
621+
Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_fastReaddirIsSupported(JNIEnv *env, jclass target) {
622+
#ifdef WINDOWS_MIN
623+
return JNI_FALSE;
624+
#else
625+
return JNI_TRUE;
626+
#endif
627+
}
628+
629+
#ifndef WINDOWS_MIN
630+
typedef struct fast_readdir_handle {
631+
HANDLE handle;
632+
wchar_t* pathStr;
633+
} readdir_fast_handle_t;
634+
#endif
635+
636+
#ifndef WINDOWS_MIN
637+
NTSTATUS invokeNtQueryDirectoryFile(HANDLE handle, BYTE* buffer, ULONG bufferSize) {
638+
IO_STATUS_BLOCK ioStatusBlock;
639+
640+
return NtQueryDirectoryFile(
641+
handle, // FileHandle
642+
NULL, // Event
643+
NULL, // ApcRoutine
644+
NULL, // ApcContext
645+
&ioStatusBlock, // IoStatusBlock
646+
buffer, // FileInformation
647+
bufferSize, // Length
648+
FileIdFullDirectoryInformation, // FileInformationClass
649+
FALSE, // ReturnSingleEntry
650+
NULL, // FileName
651+
FALSE); // RestartScan
652+
}
653+
#endif
654+
655+
//
656+
// Returns a DirectByteBuffer pointing to a |fast_readdir_handle| structure on success
657+
// Returns NULL on failure (and sets error message in |result|).
658+
//
659+
JNIEXPORT jlong JNICALL
660+
Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_fastReaddirOpen(JNIEnv *env, jclass target, jstring path, jobject result) {
661+
#ifdef WINDOWS_MIN
662+
mark_failed_with_code(env, "Operation not supported", ERROR_CALL_NOT_IMPLEMENTED, NULL, result);
663+
return NULL;
664+
#else
665+
// Open file for directory listing
666+
wchar_t* pathStr = java_to_wchar_path(env, path, result);
667+
if (pathStr == NULL) {
668+
mark_failed_with_code(env, "Out of native memory", ERROR_OUTOFMEMORY, NULL, result);
669+
return NULL;
670+
}
671+
HANDLE handle = CreateFileW(pathStr, FILE_LIST_DIRECTORY,
672+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
673+
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
674+
if (handle == INVALID_HANDLE_VALUE) {
675+
mark_failed_with_errno(env, "could not open directory", result);
676+
free(pathStr);
677+
return NULL;
678+
}
679+
readdir_fast_handle_t* readdirHandle = (readdir_fast_handle_t*)LocalAlloc(LPTR, sizeof(readdir_fast_handle_t));
680+
if (readdirHandle == NULL) {
681+
mark_failed_with_code(env, "Out of native memory", ERROR_OUTOFMEMORY, NULL, result);
682+
CloseHandle(handle);
683+
free(pathStr);
684+
return NULL;
685+
}
686+
readdirHandle->handle = handle;
687+
readdirHandle->pathStr = pathStr;
688+
return (jlong)readdirHandle;
689+
#endif
690+
}
691+
692+
//
693+
// Releases all native resources associted to the passed in |handle| (a pointer to |fast_readdir_handle_t|).
694+
//
695+
JNIEXPORT void JNICALL
696+
Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_fastReaddirClose(JNIEnv *env, jclass target, jlong handle) {
697+
#ifdef WINDOWS_MIN
698+
// Not supported
699+
#else
700+
readdir_fast_handle_t* readdirHandle = (readdir_fast_handle_t*)handle;
701+
CloseHandle(readdirHandle->handle);
702+
free(readdirHandle->pathStr);
703+
LocalFree(readdirHandle);
704+
#endif
705+
}
706+
707+
//
708+
// Reads the next batch of entries from the directory.
709+
// Returns JNI_TRUE on success and if there are more entries found
710+
// Returns JNI_FALSE and sets an error to |result| if there is an error
711+
// Returns JNI_FALSE if there are no more entries
712+
//
713+
JNIEXPORT jboolean JNICALL
714+
Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_fastReaddirNext(JNIEnv *env, jclass target, jlong handle, jobject buffer, jobject result) {
715+
#ifdef WINDOWS_MIN
716+
mark_failed_with_code(env, "Operation not supported", ERROR_CALL_NOT_IMPLEMENTED, NULL, result);
717+
return JNI_FALSE;
718+
#else
719+
readdir_fast_handle_t* readdirHandle = (readdir_fast_handle_t*)handle;
720+
721+
BYTE* entryBuffer = (BYTE*)env->GetDirectBufferAddress(buffer);
722+
ULONG entryBufferSize = (ULONG)env->GetDirectBufferCapacity(buffer);
723+
724+
NTSTATUS status = invokeNtQueryDirectoryFile(readdirHandle->handle, entryBuffer, entryBufferSize);
725+
if (!NT_SUCCESS(status)) {
726+
// Normal completion: no more files in directory
727+
if (status == STATUS_NO_MORE_FILES) {
728+
return JNI_FALSE;
729+
}
730+
731+
/*
732+
* NtQueryDirectoryFile returns STATUS_INVALID_PARAMETER when
733+
* asked to enumerate an invalid directory (ie it is a file
734+
* instead of a directory). Verify that is the actual cause
735+
* of the error.
736+
*/
737+
if (status == STATUS_INVALID_PARAMETER) {
738+
DWORD attributes = GetFileAttributesW(readdirHandle->pathStr);
739+
if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
740+
status = STATUS_NOT_A_DIRECTORY;
741+
}
742+
}
743+
mark_failed_with_ntstatus(env, "Error reading directory entries", status, result);
744+
return JNI_FALSE;
745+
}
746+
747+
return JNI_TRUE;
748+
#endif
749+
}
750+
604751
/*
605752
* Console functions
606753
*/

0 commit comments

Comments
 (0)