Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows: Add support for returning file IDs from stat and readdir #38

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
17 changes: 12 additions & 5 deletions src/main/cpp/posix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,14 @@ void unpackStat(struct stat* source, file_stat_t* result) {
#else
result->lastModified = toMillis(source->st_mtimespec);
#endif
result->volumeId = (jint)source->st_dev;
result->fileId = (jlong)source->st_ino;
}

JNIEXPORT void JNICALL
Java_net_rubygrapefruit_platform_internal_jni_PosixFileFunctions_stat(JNIEnv *env, jclass target, jstring path, jboolean followLink, jobject dest, jobject result) {
jclass destClass = env->GetObjectClass(dest);
jmethodID mid = env->GetMethodID(destClass, "details", "(IIIIJJI)V");
jmethodID mid = env->GetMethodID(destClass, "details", "(IIIIJJIIJ)V");
if (mid == NULL) {
mark_failed_with_message(env, "could not find method", result);
return;
Expand All @@ -134,7 +136,7 @@ Java_net_rubygrapefruit_platform_internal_jni_PosixFileFunctions_stat(JNIEnv *en
}

if (retval != 0) {
env->CallVoidMethod(dest, mid, FILE_TYPE_MISSING, (jint)0, (jint)0, (jint)0, (jlong)0, (jlong)0, (jint)0);
env->CallVoidMethod(dest, mid, FILE_TYPE_MISSING, (jint)0, (jint)0, (jint)0, (jlong)0, (jlong)0, (jint)0, (jint)0, (jlong)0);
} else {
file_stat_t fileResult;
unpackStat(&fileInfo, &fileResult);
Expand All @@ -146,14 +148,16 @@ Java_net_rubygrapefruit_platform_internal_jni_PosixFileFunctions_stat(JNIEnv *en
(jint)fileInfo.st_gid,
fileResult.size,
fileResult.lastModified,
(jint)fileInfo.st_blksize);
(jint)fileInfo.st_blksize,
fileResult.volumeId,
fileResult.fileId);
}
}

JNIEXPORT void JNICALL
Java_net_rubygrapefruit_platform_internal_jni_PosixFileFunctions_readdir(JNIEnv *env, jclass target, jstring path, jboolean followLink, jobject contents, jobject result) {
jclass contentsClass = env->GetObjectClass(contents);
jmethodID mid = env->GetMethodID(contentsClass, "addFile", "(Ljava/lang/String;IJJ)V");
jmethodID mid = env->GetMethodID(contentsClass, "addFile", "(Ljava/lang/String;IJJIJ)V");
if (mid == NULL) {
mark_failed_with_message(env, "could not find method", result);
return;
Expand Down Expand Up @@ -207,12 +211,15 @@ Java_net_rubygrapefruit_platform_internal_jni_PosixFileFunctions_readdir(JNIEnv
fileResult.fileType = FILE_TYPE_MISSING;
fileResult.size = 0;
fileResult.lastModified = 0;
fileResult.volumeId = 0;
fileResult.fileId = 0;
} else {
unpackStat(&fileInfo, &fileResult);
}

jstring childName = char_to_java(env, entry.d_name, result);
env->CallVoidMethod(contents, mid, childName, fileResult.fileType, fileResult.size, fileResult.lastModified);
env->CallVoidMethod(contents, mid, childName, fileResult.fileType, fileResult.size, fileResult.lastModified,
fileResult.volumeId, fileResult.fileId);
}

closedir(dir);
Expand Down
206 changes: 201 additions & 5 deletions src/main/cpp/win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
#include <windows.h>
#include <Shlwapi.h>
#include <wchar.h>
#ifndef WINDOWS_MIN
#include "ntifs_min.h"
#endif

#define ALL_COLORS (FOREGROUND_BLUE|FOREGROUND_RED|FOREGROUND_GREEN)

Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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.
Expand All @@ -179,11 +189,15 @@ DWORD get_file_stat(wchar_t* pathStr, jboolean followLink, file_stat_t* pFileSta
pFileStat->lastModified = 0;
pFileStat->size = 0;
pFileStat->fileType = FILE_TYPE_MISSING;
pFileStat->volumeId = 0;
pFileStat->fileId = 0;
return ERROR_SUCCESS;
}
return error;
}
pFileStat->lastModified = lastModifiedNanos(&attr.ftLastWriteTime);
pFileStat->volumeId = 0;
pFileStat->fileId = 0;
if (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
pFileStat->size = 0;
pFileStat->fileType = FILE_TYPE_DIRECTORY;
Expand Down Expand Up @@ -213,6 +227,8 @@ DWORD get_file_stat(wchar_t* pathStr, jboolean followLink, file_stat_t* pFileSta
pFileStat->lastModified = 0;
pFileStat->size = 0;
pFileStat->fileType = FILE_TYPE_MISSING;
pFileStat->volumeId = 0;
pFileStat->fileId = 0;
return ERROR_SUCCESS;
}
return error;
Expand Down Expand Up @@ -240,6 +256,8 @@ DWORD get_file_stat(wchar_t* pathStr, jboolean followLink, file_stat_t* pFileSta

pFileStat->lastModified = lastModifiedNanos(&fileInfo.ftLastWriteTime);
pFileStat->size = 0;
pFileStat->volumeId = fileInfo.dwVolumeSerialNumber;
pFileStat->fileId = ((LONGLONG)fileInfo.nFileIndexHigh << 32) | fileInfo.nFileIndexLow;
if (is_file_symlink(fileTagInfo.FileAttributes, fileTagInfo.ReparseTag)) {
pFileStat->fileType = FILE_TYPE_SYMLINK;
} else if (fileTagInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
Expand Down Expand Up @@ -514,12 +532,21 @@ Java_net_rubygrapefruit_platform_internal_jni_FileEventFunctions_closeWatch(JNIE

JNIEXPORT void JNICALL
Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_stat(JNIEnv *env, jclass target, jstring path, jboolean followLink, jobject dest, jobject result) {
#ifdef WINDOWS_MIN
jclass destClass = env->GetObjectClass(dest);
jmethodID mid = env->GetMethodID(destClass, "details", "(IJJ)V");
if (mid == NULL) {
mark_failed_with_message(env, "could not find method", result);
return;
}
#else
jclass destClass = env->GetObjectClass(dest);
jmethodID mid = env->GetMethodID(destClass, "details", "(IJJIJ)V");
if (mid == NULL) {
mark_failed_with_message(env, "could not find method", result);
return;
}
#endif

wchar_t* pathStr = java_to_wchar_path(env, path, result);
file_stat_t fileStat;
Expand All @@ -529,7 +556,12 @@ Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_stat(JNIEnv *
mark_failed_with_code(env, "could not file attributes", errorCode, NULL, result);
return;
}

#ifdef WINDOWS_MIN
env->CallVoidMethod(dest, mid, fileStat.fileType, fileStat.size, fileStat.lastModified);
#else
env->CallVoidMethod(dest, mid, fileStat.fileType, fileStat.size, fileStat.lastModified, fileStat.volumeId, fileStat.fileId);
#endif
}

JNIEXPORT void JNICALL
Expand Down Expand Up @@ -577,6 +609,8 @@ Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_readdir(JNIEn
FILE_TYPE_FILE;
fileInfo.lastModified = lastModifiedNanos(&entry.ftLastWriteTime);
fileInfo.size = ((jlong)entry.nFileSizeHigh << 32) | entry.nFileSizeLow;
fileInfo.volumeId = 0;
fileInfo.fileId = 0;
}

// Add entry
Expand All @@ -593,6 +627,168 @@ Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_readdir(JNIEn
FindClose(dirHandle);
}

//
// 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) {
#ifdef WINDOWS_MIN
return JNI_FALSE;
#else
return JNI_TRUE;
#endif
}

#ifndef WINDOWS_MIN
typedef struct fast_readdir_handle {
HANDLE handle;
wchar_t* pathStr;
ULONG volumeSerialNumber;
} 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

//
// 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
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;
}

// This call allows retrieving the volume ID of this directory (and all its entries)
BY_HANDLE_FILE_INFORMATION fileInfo;
BOOL ok = GetFileInformationByHandle(handle, &fileInfo);
if (!ok) {
mark_failed_with_errno(env, "could not open directory", result);
free(pathStr);
CloseHandle(handle);
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;
readdirHandle->volumeSerialNumber = fileInfo.dwVolumeSerialNumber;
return (jlong)readdirHandle;
#endif
}

//
// 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) {
#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
}

//
// Returns the volume id of the directory opened by fastReaddirOpen
//
JNIEXPORT jint JNICALL
Java_net_rubygrapefruit_platform_internal_jni_WindowsFileFunctions_fastReaddirGetVolumeId(JNIEnv *env, jclass target, jlong handle, jobject result) {
#ifdef WINDOWS_MIN
mark_failed_with_code(env, "Operation not supported", ERROR_CALL_NOT_IMPLEMENTED, NULL, result);
return 0;
#else
readdir_fast_handle_t* readdirHandle = (readdir_fast_handle_t*)handle;
return readdirHandle->volumeSerialNumber;
#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
*/
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/net/rubygrapefruit/platform/file/FileInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,12 @@ enum Type {
* Returns the last modification time of this file, in ms since epoch. Returns 0 when this file does not exist.
*/
long getLastModifiedTime();

/**
* Returns an object that uniquely identifies the given file, or null if a file key is not available.
*
* <p>See <a href="https://docs.oracle.com/javase/7/docs/api/java/nio/file/attribute/BasicFileAttributes.html#fileKey()">BasicFileAttributes.fileKey()</a>
* for a more in depth explanation.</p>
*/
Object getKey();
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,13 @@
*/
@ThreadSafe
public interface WindowsFileInfo extends FileInfo {
/**
* Returns the volume ID (serial number) of the file.
*/
int getVolumeId();

/**
* Returns the file ID of the file, unique within the volume identified by {@link #getVolumeId()}.
*/
long getFileId();
}
Loading