From cc920663dd01a0188417d1fcf3e5463c3bc8bc76 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Fri, 10 May 2019 11:20:21 -0700 Subject: [PATCH 1/2] 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 --- build.gradle | 3 + src/main/cpp/win.cpp | 154 ++++++++++++- .../internal/DefaultWindowsFiles.java | 206 +++++++++++++++++- .../platform/internal/WindowsDirList.java | 6 + .../internal/jni/WindowsFileFunctions.java | 8 + src/shared/headers/ntifs_min.h | 149 +++++++++++++ 6 files changed, 513 insertions(+), 13 deletions(-) create mode 100644 src/shared/headers/ntifs_min.h diff --git a/build.gradle b/build.gradle index 28d8464e..7b99f285 100755 --- a/build.gradle +++ b/build.gradle @@ -274,6 +274,9 @@ model { } else if (targetPlatform.operatingSystem.windows) { if (name.contains("_min")) { cppCompiler.define "WINDOWS_MIN" + } else { + // For NtQueryDirectoryFile + linker.args "ntdll.lib" } cppCompiler.args "-I${org.gradle.internal.jvm.Jvm.current().javaHome}/include" cppCompiler.args "-I${org.gradle.internal.jvm.Jvm.current().javaHome}/include/win32" diff --git a/src/main/cpp/win.cpp b/src/main/cpp/win.cpp index df0a1ac5..94489b36 100755 --- a/src/main/cpp/win.cpp +++ b/src/main/cpp/win.cpp @@ -21,6 +21,9 @@ #include #include #include +#ifndef WINDOWS_MIN +#include "ntifs_min.h" +#endif #define ALL_COLORS (FOREGROUND_BLUE|FOREGROUND_RED|FOREGROUND_GREEN) @@ -31,8 +34,18 @@ void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result) { mark_failed_with_code(env, message, GetLastError(), NULL, result); } +#ifndef WINDOWS_MIN +/* + * Marks the given result as failed, using a NTSTATUS error code + */ +void mark_failed_with_ntstatus(JNIEnv *env, const char* message, NTSTATUS status, jobject result) { + ULONG win32ErrorCode = RtlNtStatusToDosError(status); + mark_failed_with_code(env, message, win32ErrorCode, NULL, result); +} +#endif + int map_error_code(int error_code) { - if (error_code == ERROR_PATH_NOT_FOUND) { + if (error_code == ERROR_FILE_NOT_FOUND || error_code == ERROR_PATH_NOT_FOUND) { return FAILURE_NO_SUCH_FILE; } if (error_code == ERROR_DIRECTORY) { @@ -81,6 +94,7 @@ bool is_path_absolute_unc(wchar_t* path, int path_len) { // // Returns a UTF-16 string that is the concatenation of |prefix| and |path|. +// The string must be deallocated with a call to free(). // wchar_t* add_prefix(wchar_t* path, int path_len, wchar_t* prefix) { int prefix_len = wcslen(prefix); @@ -156,10 +170,6 @@ jlong lastModifiedNanos(FILETIME* time) { return ((jlong)time->dwHighDateTime << 32) | time->dwLowDateTime; } -jlong lastModifiedNanos(LARGE_INTEGER* time) { - return ((jlong)time->HighPart << 32) | time->LowPart; -} - // // Retrieves the file attributes for the file specified by |pathStr|. // If |followLink| is true, symbolic link targets are resolved. @@ -593,6 +603,140 @@ Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_readdir(JNIEn FindClose(dirHandle); } +// +// Returns "true" is the various fastReaddirXxx calls are supported on this platform. +// +JNIEXPORT jboolean JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_fastReaddirIsSupported(JNIEnv *env, jclass target) { +#ifdef WINDOWS_MIN + return JNI_FALSE; +#else + return JNI_TRUE; +#endif +} + +#ifndef WINDOWS_MIN +typedef struct fast_readdir_handle { + HANDLE handle; + wchar_t* pathStr; +} readdir_fast_handle_t; +#endif + +#ifndef WINDOWS_MIN +NTSTATUS invokeNtQueryDirectoryFile(HANDLE handle, BYTE* buffer, ULONG bufferSize) { + IO_STATUS_BLOCK ioStatusBlock; + + return NtQueryDirectoryFile( + handle, // FileHandle + NULL, // Event + NULL, // ApcRoutine + NULL, // ApcContext + &ioStatusBlock, // IoStatusBlock + buffer, // FileInformation + bufferSize, // Length + FileIdFullDirectoryInformation, // FileInformationClass + FALSE, // ReturnSingleEntry + NULL, // FileName + FALSE); // RestartScan +} +#endif + +// +// Returns a DirectByteBuffer pointing to a |fast_readdir_handle| structure on success +// Returns NULL on failure (and sets error message in |result|). +// +JNIEXPORT jlong JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_fastReaddirOpen(JNIEnv *env, jclass target, jstring path, jobject result) { +#ifdef WINDOWS_MIN + mark_failed_with_code(env, "Operation not supported", ERROR_CALL_NOT_IMPLEMENTED, NULL, result); + return NULL; +#else + // Open file for directory listing + wchar_t* pathStr = java_to_wchar_path(env, path, result); + if (pathStr == NULL) { + mark_failed_with_code(env, "Out of native memory", ERROR_OUTOFMEMORY, NULL, result); + return NULL; + } + HANDLE handle = CreateFileW(pathStr, FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (handle == INVALID_HANDLE_VALUE) { + mark_failed_with_errno(env, "could not open directory", result); + free(pathStr); + return NULL; + } + readdir_fast_handle_t* readdirHandle = (readdir_fast_handle_t*)LocalAlloc(LPTR, sizeof(readdir_fast_handle_t)); + if (readdirHandle == NULL) { + mark_failed_with_code(env, "Out of native memory", ERROR_OUTOFMEMORY, NULL, result); + CloseHandle(handle); + free(pathStr); + return NULL; + } + readdirHandle->handle = handle; + readdirHandle->pathStr = pathStr; + return (jlong)readdirHandle; +#endif +} + +// +// Releases all native resources associted to the passed in |handle| (a pointer to |fast_readdir_handle_t|). +// +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_fastReaddirClose(JNIEnv *env, jclass target, jlong handle) { +#ifdef WINDOWS_MIN + // Not supported +#else + readdir_fast_handle_t* readdirHandle = (readdir_fast_handle_t*)handle; + CloseHandle(readdirHandle->handle); + free(readdirHandle->pathStr); + LocalFree(readdirHandle); +#endif +} + +// +// Reads the next batch of entries from the directory. +// Returns JNI_TRUE on success and if there are more entries found +// Returns JNI_FALSE and sets an error to |result| if there is an error +// Returns JNI_FALSE if there are no more entries +// +JNIEXPORT jboolean JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_fastReaddirNext(JNIEnv *env, jclass target, jlong handle, jobject buffer, jobject result) { +#ifdef WINDOWS_MIN + mark_failed_with_code(env, "Operation not supported", ERROR_CALL_NOT_IMPLEMENTED, NULL, result); + return JNI_FALSE; +#else + readdir_fast_handle_t* readdirHandle = (readdir_fast_handle_t*)handle; + + BYTE* entryBuffer = (BYTE*)env->GetDirectBufferAddress(buffer); + ULONG entryBufferSize = (ULONG)env->GetDirectBufferCapacity(buffer); + + NTSTATUS status = invokeNtQueryDirectoryFile(readdirHandle->handle, entryBuffer, entryBufferSize); + if (!NT_SUCCESS(status)) { + // Normal completion: no more files in directory + if (status == STATUS_NO_MORE_FILES) { + return JNI_FALSE; + } + + /* + * NtQueryDirectoryFile returns STATUS_INVALID_PARAMETER when + * asked to enumerate an invalid directory (ie it is a file + * instead of a directory). Verify that is the actual cause + * of the error. + */ + if (status == STATUS_INVALID_PARAMETER) { + DWORD attributes = GetFileAttributesW(readdirHandle->pathStr); + if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { + status = STATUS_NOT_A_DIRECTORY; + } + } + mark_failed_with_ntstatus(env, "Error reading directory entries", status, result); + return JNI_FALSE; + } + + return JNI_TRUE; +#endif +} + /* * Console functions */ diff --git a/src/main/java/net/rubygrapefruit/platform/internal/DefaultWindowsFiles.java b/src/main/java/net/rubygrapefruit/platform/internal/DefaultWindowsFiles.java index 5db7579b..6eaa3958 100644 --- a/src/main/java/net/rubygrapefruit/platform/internal/DefaultWindowsFiles.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/DefaultWindowsFiles.java @@ -24,9 +24,19 @@ import net.rubygrapefruit.platform.internal.jni.WindowsFileFunctions; import java.io.File; +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.CharBuffer; import java.util.List; public class DefaultWindowsFiles extends AbstractFiles implements WindowsFiles { + private static final int FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400; + private static final int FILE_ATTRIBUTE_DIRECTORY = 0x00000010; + private static final int IO_REPARSE_TAG_SYMLINK = 0xA000000C; + + private DirectoryLister directoryLister; + public WindowsFileInfo stat(File file) throws NativeException { return stat(file, false); } @@ -41,17 +51,197 @@ public WindowsFileInfo stat(File file, boolean linkTarget) throws NativeExceptio return stat; } + public List listDir(File dir) throws NativeException { + return listDir(dir, false); + } + public List listDir(File dir, boolean linkTarget) throws NativeException { - FunctionResult result = new FunctionResult(); - WindowsDirList dirList = new WindowsDirList(); - WindowsFileFunctions.readdir(dir.getPath(), linkTarget, dirList, result); - if (result.isFailed()) { - throw listDirFailure(dir, result); + if (directoryLister == null) { + directoryLister = WindowsFileFunctions.fastReaddirIsSupported() ? new FastLister() : new BackwardCompatibleLister(); } - return dirList.files; + return directoryLister.listDir(dir, linkTarget); } - public List listDir(File dir) throws NativeException { - return listDir(dir, false); + private interface DirectoryLister { + List listDir(File dir, boolean linkTarget) throws NativeException; + } + + private class BackwardCompatibleLister implements DirectoryLister { + public List listDir(File dir, boolean linkTarget) throws NativeException { + FunctionResult result = new FunctionResult(); + WindowsDirList dirList = new WindowsDirList(); + WindowsFileFunctions.readdir(dir.getPath(), linkTarget, dirList, result); + if (result.isFailed()) { + throw listDirFailure(dir, result); + } + return dirList.files; + } + } + + private class FastLister implements DirectoryLister { + static final int SIZEOF_WCHAR = 2; + static final int OFFSETOF_NEXT_ENTRY_OFFSET = 0; + static final int OFFSETOF_LAST_WRITE_TIME = 24; + static final int OFFSETOF_END_OF_FILE = 40; + static final int OFFSETOF_FILE_ATTRIBUTES = 56; + static final int OFFSETOF_FILENAME_LENGTH = 60; + static final int OFFSETOF_EA_SIZE = 64; + static final int OFFSETOF_FILENAME = 80; + + /** + * We use a thread local context to store a weak reference to a {@link NtQueryDirectoryFileContext} + * instance so that we can avoid an extra direct {@link ByteBuffer} allocation at every + * invocation of {@link #listDir(File, boolean)}. + *

This has shown to improve performance by a few percents in stress test benchmarks, while at the + * same time limiting memory usage increase to a minimum (about 8KB per thread using this API)

+ *

This is safe because {@link #listDir(File, boolean)} invocations are self-contained, i.e. don't + * call back into external code.

+ */ + private final ThreadLocal> threadLocalContext = + new ThreadLocal>(); + + public List listDir(File dir, boolean linkTarget) throws NativeException { + FunctionResult result = new FunctionResult(); + WindowsDirList dirList = new WindowsDirList(); + String path = dir.getPath(); + + long handle = WindowsFileFunctions.fastReaddirOpen(path, result); + if (result.isFailed()) { + throw listDirFailure(dir, result); + } + try { + NtQueryDirectoryFileContext context = getNtQueryDirectoryFileContext(); + + boolean more = WindowsFileFunctions.fastReaddirNext(handle, context.buffer, result); + if (result.isFailed()) { + throw listDirFailure(dir, result); + } + if (more) { + int entryOffset = 0; + while (true) { + // Read entry from buffer + entryOffset = addFullDirEntry(context, dir, linkTarget, entryOffset, dirList); + + // If we reached end of buffer, fetch next set of entries + if (entryOffset == 0) { + more = WindowsFileFunctions.fastReaddirNext(handle, context.buffer, result); + if (result.isFailed()) { + throw listDirFailure(dir, result); + } + if (!more) { + break; + } + } + } + } + } finally { + WindowsFileFunctions.fastReaddirClose(handle); + } + + return dirList.files; + } + + /** + * Parse the content of {@link NtQueryDirectoryFileContext#buffer} at offset {@code entryOffset} + * as a FILE_ID_FULL_DIR_INFORMATION + * native structure and adds the parsed entry into the {@link WindowsDirList} collection. + *

Returns the byte offset of the next entry in {@link NtQueryDirectoryFileContext#buffer} if there is one, + * or {@code 0} if there is no next entry.

+ */ + private int addFullDirEntry(NtQueryDirectoryFileContext context, File dir, boolean followLink, int entryOffset, WindowsDirList dirList) { + // typedef struct _FILE_ID_FULL_DIR_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 + // ULONG EaSize; // offset = 64 + // LARGE_INTEGER FileId; // offset = 72 + // WCHAR FileName[1]; // offset = 80 + //} FILE_ID_FULL_DIR_INFORMATION, *PFILE_ID_FULL_DIR_INFORMATION; + int nextEntryOffset = context.buffer.getInt(entryOffset + OFFSETOF_NEXT_ENTRY_OFFSET); + + long fileSize = context.buffer.getLong(entryOffset + OFFSETOF_END_OF_FILE); + long lastModified = context.buffer.getLong(entryOffset + OFFSETOF_LAST_WRITE_TIME); + + // + // See https://docs.microsoft.com/en-us/windows/desktop/fileio/reparse-point-tags + // IO_REPARSE_TAG_SYMLINK (0xA000000C) + // + int fileAttributes = context.buffer.getInt(entryOffset + OFFSETOF_FILE_ATTRIBUTES); + int reparseTagData = context.buffer.getInt(entryOffset + OFFSETOF_EA_SIZE); + + FileInfo.Type type = getFileType(fileAttributes, reparseTagData); + + int FileNameByteCount = context.buffer.getInt(entryOffset + OFFSETOF_FILENAME_LENGTH); + context.charBuffer.position((entryOffset + OFFSETOF_FILENAME) / SIZEOF_WCHAR); + context.charBuffer.get(context.fileNameArray, 0, FileNameByteCount / SIZEOF_WCHAR); + String fileName = new String(context.fileNameArray, 0, FileNameByteCount / SIZEOF_WCHAR); + + // Skip "." and ".." entries + if (!".".equals(fileName) && !"..".equals(fileName)) { + if (type == FileInfo.Type.Symlink && followLink) { + WindowsFileInfo targetInfo = stat(new File(dir, fileName), true); + dirList.addFile(fileName, targetInfo); + } else { + dirList.addFile(fileName, type.ordinal(), fileSize, lastModified); + } + } + + return nextEntryOffset == 0 ? 0 : entryOffset + nextEntryOffset; + } + + private FileInfo.Type getFileType(int dwFileAttributes, int reparseTagData) { + if (((dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT) && (reparseTagData == IO_REPARSE_TAG_SYMLINK)) { + return FileInfo.Type.Symlink; + } else if ((dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) { + return FileInfo.Type.Directory; + } else { + return FileInfo.Type.File; + } + } + + private NtQueryDirectoryFileContext getNtQueryDirectoryFileContext() { + WeakReference ref = threadLocalContext.get(); + NtQueryDirectoryFileContext result = (ref == null ? null : ref.get()); + if (result == null) { + result = new NtQueryDirectoryFileContext(); + ref = new WeakReference(result); + threadLocalContext.set(ref); + } + return result; + } + + private class NtQueryDirectoryFileContext { + /** + * The {@code direct} {@link ByteBuffer} used to share memory between C++ and Java code. + */ + final ByteBuffer buffer; + /** + * A {@link CharBuffer} view of the {@link #buffer} above used to retrieve UTF-16 filename strings. + */ + final CharBuffer charBuffer; + /** + * A {@link java.lang.Character} array used to copy characters from the {@link #charBuffer} above + * before converting into a {@link java.lang.String} instance. + */ + final char[] fileNameArray; + + NtQueryDirectoryFileContext() { + // Note: 8KB is enough to store ~100 FILE_ID_FULL_DIR_INFORMATION entries, counting 80 bytes + // as "fixed" data plus 10 UTF-16 characters for the file name. + buffer = ByteBuffer.allocateDirect(8192); + // Win32/x86 is little endian + buffer.order(ByteOrder.LITTLE_ENDIAN); + charBuffer = buffer.asCharBuffer(); + // Note: Win32 file names are limited to 256 characters + fileNameArray = new char[256]; + } + } } } diff --git a/src/main/java/net/rubygrapefruit/platform/internal/WindowsDirList.java b/src/main/java/net/rubygrapefruit/platform/internal/WindowsDirList.java index 319fac34..0054c925 100644 --- a/src/main/java/net/rubygrapefruit/platform/internal/WindowsDirList.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/WindowsDirList.java @@ -16,6 +16,8 @@ package net.rubygrapefruit.platform.internal; +import net.rubygrapefruit.platform.file.WindowsFileInfo; + public class WindowsDirList extends DirList { // Called from native code @SuppressWarnings("UnusedDeclaration") @@ -23,4 +25,8 @@ public class WindowsDirList extends DirList { public void addFile(String name, int type, long size, long lastModified) { super.addFile(name, type, size, WindowsFileTime.toJavaTime(lastModified)); } + + public void addFile(String name, WindowsFileInfo fileInfo) { + super.addFile(name, fileInfo.getType().ordinal(), fileInfo.getSize(), fileInfo.getLastModifiedTime()); + } } diff --git a/src/main/java/net/rubygrapefruit/platform/internal/jni/WindowsFileFunctions.java b/src/main/java/net/rubygrapefruit/platform/internal/jni/WindowsFileFunctions.java index a192338f..1e75e99d 100644 --- a/src/main/java/net/rubygrapefruit/platform/internal/jni/WindowsFileFunctions.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/jni/WindowsFileFunctions.java @@ -20,8 +20,16 @@ import net.rubygrapefruit.platform.internal.FunctionResult; import net.rubygrapefruit.platform.internal.WindowsFileStat; +import java.nio.ByteBuffer; + +@SuppressWarnings("SpellCheckingInspection") public class WindowsFileFunctions { public static native void stat(String file, boolean followLink, WindowsFileStat stat, FunctionResult result); public static native void readdir(String path, boolean followLink, DirList dirList, FunctionResult result); + + public static native boolean fastReaddirIsSupported(); + public static native long fastReaddirOpen(String path, FunctionResult result); + public static native void fastReaddirClose(long handle); + public static native boolean fastReaddirNext(long handle, ByteBuffer buffer, FunctionResult result); } diff --git a/src/shared/headers/ntifs_min.h b/src/shared/headers/ntifs_min.h new file mode 100644 index 00000000..9206c38e --- /dev/null +++ b/src/shared/headers/ntifs_min.h @@ -0,0 +1,149 @@ +#ifndef _NTIFS_MIN_ +#define _NTIFS_MIN_ + +extern "C" { + +/* +* Copy necessary structures and definitions out of the Windows DDK +* to enable calling NtQueryDirectoryFile() +*/ + +typedef _Return_type_success_(return >= 0) LONG NTSTATUS; +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) + +typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; +#ifdef MIDL_PASS + [size_is(MaximumLength / 2), length_is((Length) / 2)] USHORT * Buffer; +#else // MIDL_PASS + _Field_size_bytes_part_(MaximumLength, Length) PWCH Buffer; +#endif // MIDL_PASS +} UNICODE_STRING; +typedef UNICODE_STRING *PUNICODE_STRING; +typedef const UNICODE_STRING *PCUNICODE_STRING; + +typedef enum _FILE_INFORMATION_CLASS { + FileDirectoryInformation = 1, + FileFullDirectoryInformation, + FileBothDirectoryInformation, + FileBasicInformation, + FileStandardInformation, + FileInternalInformation, + FileEaInformation, + FileAccessInformation, + FileNameInformation, + FileRenameInformation, + FileLinkInformation, + FileNamesInformation, + FileDispositionInformation, + FilePositionInformation, + FileFullEaInformation, + FileModeInformation, + FileAlignmentInformation, + FileAllInformation, + FileAllocationInformation, + FileEndOfFileInformation, + FileAlternateNameInformation, + FileStreamInformation, + FilePipeInformation, + FilePipeLocalInformation, + FilePipeRemoteInformation, + FileMailslotQueryInformation, + FileMailslotSetInformation, + FileCompressionInformation, + FileObjectIdInformation, + FileCompletionInformation, + FileMoveClusterInformation, + FileQuotaInformation, + FileReparsePointInformation, + FileNetworkOpenInformation, + FileAttributeTagInformation, + FileTrackingInformation, + FileIdBothDirectoryInformation, + FileIdFullDirectoryInformation, + FileValidDataLengthInformation, + FileShortNameInformation, + FileIoCompletionNotificationInformation, + FileIoStatusBlockRangeInformation, + FileIoPriorityHintInformation, + FileSfioReserveInformation, + FileSfioVolumeInformation, + FileHardLinkInformation, + FileProcessIdsUsingFileInformation, + FileNormalizedNameInformation, + FileNetworkPhysicalNameInformation, + FileIdGlobalTxDirectoryInformation, + FileIsRemoteDeviceInformation, + FileAttributeCacheInformation, + FileNumaNodeInformation, + FileStandardLinkInformation, + FileRemoteProtocolInformation, + FileMaximumInformation +} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS; + +typedef struct _FILE_ID_FULL_DIR_INFORMATION { + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + LARGE_INTEGER FileId; + WCHAR FileName[1]; +} FILE_ID_FULL_DIR_INFORMATION, *PFILE_ID_FULL_DIR_INFORMATION; + +typedef struct _IO_STATUS_BLOCK { + union { + NTSTATUS Status; + PVOID Pointer; + } u; + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + +typedef VOID +(NTAPI *PIO_APC_ROUTINE)( + IN PVOID ApcContext, + IN PIO_STATUS_BLOCK IoStatusBlock, + IN ULONG Reserved); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryDirectoryFile( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_writes_bytes_(Length) PVOID FileInformation, + _In_ ULONG Length, + _In_ FILE_INFORMATION_CLASS FileInformationClass, + _In_ BOOLEAN ReturnSingleEntry, + _In_opt_ PUNICODE_STRING FileName, + _In_ BOOLEAN RestartScan +); + +// +// This function might be needed for some of the internal Windows functions, +// defined in this header file. +// +_When_(Status < 0, _Out_range_(>, 0)) +_When_(Status >= 0, _Out_range_(==, 0)) +ULONG +NTAPI +RtlNtStatusToDosError ( + NTSTATUS Status +); + +#define STATUS_NO_MORE_FILES ((NTSTATUS)0x80000006L) +#define STATUS_NOT_A_DIRECTORY ((NTSTATUS)0xC0000103L) + +} // extern "C" + +#endif // _NTIFS_MIN_ \ No newline at end of file From 32305373c4d7006dbfe445793cd7a2418849c06c Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 7 Aug 2019 12:20:46 -0700 Subject: [PATCH 2/2] Fix typos in comments, minor code cleanup. --- src/main/cpp/win.cpp | 7 +++--- .../internal/DefaultWindowsFiles.java | 24 +++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main/cpp/win.cpp b/src/main/cpp/win.cpp index 94489b36..5a103bf8 100755 --- a/src/main/cpp/win.cpp +++ b/src/main/cpp/win.cpp @@ -604,7 +604,7 @@ Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_readdir(JNIEn } // -// Returns "true" is the various fastReaddirXxx calls are supported on this platform. +// Returns "true" if the various fastReaddirXxx calls are supported on this platform. // JNIEXPORT jboolean JNICALL Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_fastReaddirIsSupported(JNIEnv *env, jclass target) { @@ -642,7 +642,8 @@ NTSTATUS invokeNtQueryDirectoryFile(HANDLE handle, BYTE* buffer, ULONG bufferSiz #endif // -// Returns a DirectByteBuffer pointing to a |fast_readdir_handle| structure on success +// Opens a directory for file enumeration and returns a handle to a |fast_readdir_handle| structure +// on success. The handle must be released by calling "xxx_fastReaddirClose" when done. // Returns NULL on failure (and sets error message in |result|). // JNIEXPORT jlong JNICALL @@ -679,7 +680,7 @@ Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_fastReaddirOp } // -// Releases all native resources associted to the passed in |handle| (a pointer to |fast_readdir_handle_t|). +// Releases all native resources associated with |handle|, a pointer to |fast_readdir_handle|. // JNIEXPORT void JNICALL Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_fastReaddirClose(JNIEnv *env, jclass target, jlong handle) { diff --git a/src/main/java/net/rubygrapefruit/platform/internal/DefaultWindowsFiles.java b/src/main/java/net/rubygrapefruit/platform/internal/DefaultWindowsFiles.java index 6eaa3958..0f4e9016 100644 --- a/src/main/java/net/rubygrapefruit/platform/internal/DefaultWindowsFiles.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/DefaultWindowsFiles.java @@ -31,10 +31,6 @@ import java.util.List; public class DefaultWindowsFiles extends AbstractFiles implements WindowsFiles { - private static final int FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400; - private static final int FILE_ATTRIBUTE_DIRECTORY = 0x00000010; - private static final int IO_REPARSE_TAG_SYMLINK = 0xA000000C; - private DirectoryLister directoryLister; public WindowsFileInfo stat(File file) throws NativeException { @@ -79,14 +75,18 @@ public List listDir(File dir, boolean linkTarget) throws Nat } private class FastLister implements DirectoryLister { - static final int SIZEOF_WCHAR = 2; - static final int OFFSETOF_NEXT_ENTRY_OFFSET = 0; - static final int OFFSETOF_LAST_WRITE_TIME = 24; - static final int OFFSETOF_END_OF_FILE = 40; - static final int OFFSETOF_FILE_ATTRIBUTES = 56; - static final int OFFSETOF_FILENAME_LENGTH = 60; - static final int OFFSETOF_EA_SIZE = 64; - static final int OFFSETOF_FILENAME = 80; + private static final int FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400; + private static final int FILE_ATTRIBUTE_DIRECTORY = 0x00000010; + private static final int IO_REPARSE_TAG_SYMLINK = 0xA000000C; + + private static final int SIZEOF_WCHAR = 2; + private static final int OFFSETOF_NEXT_ENTRY_OFFSET = 0; + private static final int OFFSETOF_LAST_WRITE_TIME = 24; + private static final int OFFSETOF_END_OF_FILE = 40; + private static final int OFFSETOF_FILE_ATTRIBUTES = 56; + private static final int OFFSETOF_FILENAME_LENGTH = 60; + private static final int OFFSETOF_EA_SIZE = 64; + private static final int OFFSETOF_FILENAME = 80; /** * We use a thread local context to store a weak reference to a {@link NtQueryDirectoryFileContext}