| /* |
| * Copyright 2000-2012 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "IdeaWin32.h" |
| #include <windows.h> |
| |
| typedef DWORD (WINAPI *GetFinalPathNameByHandlePtr) (HANDLE, LPCWSTR, DWORD, DWORD dwFlags); |
| static GetFinalPathNameByHandlePtr __GetFinalPathNameByHandle = NULL; |
| |
| static jfieldID nameID = NULL; |
| static jfieldID attributesID = NULL; |
| static jfieldID timestampID = NULL; |
| static jfieldID lengthID = NULL; |
| |
| #define FILE_INFO_CLASS "com/intellij/openapi/util/io/win32/FileInfo" |
| #define BROKEN_SYMLINK_ATTR -1 |
| #define IS_SET(flags, flag) ((flags & flag) == flag) |
| #define FILE_SHARE_ATTRIBUTES (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE) |
| |
| static wchar_t* ToWinPath(JNIEnv* env, jstring path, bool dirSuffix); |
| static jobject CreateFileInfo(JNIEnv* env, wchar_t* path, bool isDirectory, LPWIN32_FIND_DATA lpData, jclass aClass); |
| static jobjectArray CopyObjectArray(JNIEnv* env, jobjectArray src, jclass aClass, jsize count, jsize newSize); |
| |
| |
| // interface methods |
| |
| JNIEXPORT void JNICALL Java_com_intellij_openapi_util_io_win32_IdeaWin32_initIDs(JNIEnv* env, jclass cls) { |
| __GetFinalPathNameByHandle = (GetFinalPathNameByHandlePtr)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "GetFinalPathNameByHandleW"); |
| |
| jclass fileInfoClass = env->FindClass(FILE_INFO_CLASS); |
| if (fileInfoClass == NULL) { |
| return; |
| } |
| |
| nameID = env->GetFieldID(fileInfoClass, "name", "Ljava/lang/String;"); |
| attributesID = env->GetFieldID(fileInfoClass, "attributes", "I"); |
| timestampID = env->GetFieldID(fileInfoClass, "timestamp", "J"); |
| lengthID = env->GetFieldID(fileInfoClass, "length", "J"); |
| } |
| |
| JNIEXPORT jobject JNICALL Java_com_intellij_openapi_util_io_win32_IdeaWin32_getInfo0(JNIEnv* env, jobject method, jstring path) { |
| wchar_t* winPath = ToWinPath(env, path, false); |
| if (winPath == NULL) { |
| return NULL; |
| } |
| |
| WIN32_FILE_ATTRIBUTE_DATA attrData; |
| BOOL res = GetFileAttributesExW(winPath, GetFileExInfoStandard, &attrData); |
| if (!res) { |
| free(winPath); |
| return NULL; |
| } |
| |
| jclass fileInfoClass = env->FindClass(FILE_INFO_CLASS); |
| if (fileInfoClass == NULL) { |
| return NULL; |
| } |
| |
| jobject result = NULL; |
| if (IS_SET(attrData.dwFileAttributes, FILE_ATTRIBUTE_REPARSE_POINT)) { |
| // may be symlink |
| WIN32_FIND_DATA data; |
| HANDLE h = FindFirstFileW(winPath, &data); |
| if (h != INVALID_HANDLE_VALUE) { |
| FindClose(h); |
| result = CreateFileInfo(env, winPath, false, &data, fileInfoClass); |
| } |
| } |
| if (result == NULL) { |
| // either not a symlink or FindFirstFile() failed |
| WIN32_FIND_DATA data; |
| data.dwFileAttributes = attrData.dwFileAttributes; |
| data.dwReserved0 = 0; |
| data.ftLastWriteTime = attrData.ftLastWriteTime; |
| data.nFileSizeLow = attrData.nFileSizeLow; |
| data.nFileSizeHigh = attrData.nFileSizeHigh; |
| data.cFileName[0] = L'\0'; |
| result = CreateFileInfo(env, winPath, false, &data, fileInfoClass); |
| } |
| |
| free(winPath); |
| return result; |
| } |
| |
| JNIEXPORT jstring JNICALL Java_com_intellij_openapi_util_io_win32_IdeaWin32_resolveSymLink0(JNIEnv* env, jobject method, jstring path) { |
| if (__GetFinalPathNameByHandle == NULL) { |
| return path; // links not supported |
| } |
| |
| wchar_t* winPath = ToWinPath(env, path, false); |
| jstring result = path; |
| |
| WIN32_FIND_DATA data; |
| HANDLE h = FindFirstFileW(winPath, &data); |
| if (h != INVALID_HANDLE_VALUE) { |
| FindClose(h); |
| |
| if (IS_SET(data.dwFileAttributes, FILE_ATTRIBUTE_REPARSE_POINT) && |
| (IS_SET(data.dwReserved0, IO_REPARSE_TAG_SYMLINK) || IS_SET(data.dwReserved0, IO_REPARSE_TAG_MOUNT_POINT))) { |
| HANDLE th = CreateFileW(winPath, 0, FILE_SHARE_ATTRIBUTES, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); |
| if (th != INVALID_HANDLE_VALUE) { |
| wchar_t buff[MAX_PATH], * finalPath = buff; |
| DWORD len = __GetFinalPathNameByHandle(th, buff, MAX_PATH, 0); |
| if (len >= MAX_PATH) { |
| finalPath = (wchar_t*)malloc((len + 1) * sizeof(wchar_t)); |
| len = finalPath != NULL ? __GetFinalPathNameByHandle(th, finalPath, len, 0) : 0; |
| } |
| if (len > 0) { |
| int prefix = (len > 4 && finalPath[0] == L'\\' && finalPath[1] == L'\\' && finalPath[2] == L'?' && finalPath[3] == L'\\') ? 4 : 0; |
| result = env->NewString((jchar*)finalPath + prefix, len - prefix); |
| if (finalPath != buff) { |
| free(finalPath); |
| } |
| } |
| CloseHandle(th); |
| } |
| else { |
| result = NULL; |
| } |
| } |
| } |
| else { |
| result = NULL; |
| } |
| |
| free(winPath); |
| return result; |
| } |
| |
| JNIEXPORT jobjectArray JNICALL Java_com_intellij_openapi_util_io_win32_IdeaWin32_listChildren0(JNIEnv* env, jobject method, jstring path) { |
| jclass fileInfoClass = env->FindClass(FILE_INFO_CLASS); |
| if (fileInfoClass == NULL) { |
| return NULL; |
| } |
| |
| wchar_t* winPath = ToWinPath(env, path, true); |
| if (winPath == NULL) { |
| return NULL; |
| } |
| |
| WIN32_FIND_DATA data; |
| HANDLE h = FindFirstFileW(winPath, &data); |
| if (h == INVALID_HANDLE_VALUE) { |
| free(winPath); |
| return NULL; |
| } |
| |
| jsize len = 0, maxLen = 16; |
| jobjectArray result = env->NewObjectArray(maxLen, fileInfoClass, NULL); |
| if (result != NULL) { |
| do { |
| if (wcscmp(data.cFileName, L".") == 0 || wcscmp(data.cFileName, L"..") == 0) { |
| continue; |
| } |
| |
| if (len == maxLen) { |
| result = CopyObjectArray(env, result, fileInfoClass, len, maxLen <<= 1); |
| if (result == NULL) { |
| goto exit; |
| } |
| } |
| |
| jobject o = CreateFileInfo(env, winPath, true, &data, fileInfoClass); |
| env->SetObjectArrayElement(result, len++, o); |
| env->DeleteLocalRef(o); |
| } |
| while (FindNextFile(h, &data)); |
| |
| if (len != maxLen) { |
| result = CopyObjectArray(env, result, fileInfoClass, len, len); |
| } |
| } |
| |
| exit: |
| free(winPath); |
| FindClose(h); |
| return result; |
| } |
| |
| BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { |
| return TRUE; |
| } |
| |
| |
| // utility methods |
| |
| static inline LONGLONG pairToInt64(DWORD lowPart, DWORD highPart); |
| |
| static wchar_t* ToWinPath(JNIEnv* env, jstring path, bool dirSuffix) { |
| jsize len = env->GetStringLength(path), prefix = 0, suffix = 0; |
| const jchar* jstr = env->GetStringChars(path, NULL); |
| while (len > 0 && jstr[len - 1] == L'\\') --len; // trim trailing separators |
| if (len == 0) return NULL; |
| if (len >= (MAX_PATH - 12)) prefix = 4; // prefix long paths by UNC marker |
| if (dirSuffix) suffix = 2; |
| |
| wchar_t* pathBuf = (wchar_t*)malloc((prefix + len + suffix + 1) * sizeof(wchar_t)); |
| if (pathBuf != NULL) { |
| if (prefix > 0) { |
| wcsncpy_s(pathBuf, prefix + 1, L"\\\\?\\", prefix); |
| } |
| wcsncpy_s(pathBuf + prefix, len + 1, (wchar_t*)jstr, len); |
| if (suffix > 0) { |
| wcsncpy_s(pathBuf + prefix + len, suffix + 1, L"\\*", suffix); |
| } |
| pathBuf[prefix + len + suffix] = L'\0'; |
| } |
| |
| env->ReleaseStringChars(path, jstr); |
| |
| return pathBuf; |
| } |
| |
| static jobject CreateFileInfo(JNIEnv* env, wchar_t* path, bool isDirectory, LPWIN32_FIND_DATA lpData, jclass aClass) { |
| DWORD attributes = lpData->dwFileAttributes; |
| LONGLONG timestamp = pairToInt64(lpData->ftLastWriteTime.dwLowDateTime, lpData->ftLastWriteTime.dwHighDateTime); |
| LONGLONG length = pairToInt64(lpData->nFileSizeLow, lpData->nFileSizeHigh); |
| |
| if (IS_SET(attributes, FILE_ATTRIBUTE_REPARSE_POINT)) { |
| if (IS_SET(lpData->dwReserved0, IO_REPARSE_TAG_SYMLINK)) { |
| attributes = BROKEN_SYMLINK_ATTR; |
| timestamp = 0; |
| length = 0; |
| |
| wchar_t* fullPath = path; |
| if (isDirectory) { |
| // trim '*' and append file name |
| size_t dirLen = wcslen(path) - 1, nameLen = wcslen(lpData->cFileName), fullLen = dirLen + nameLen + 1; |
| fullPath = (wchar_t*)malloc(fullLen * sizeof(wchar_t)); |
| if (fullPath == NULL) { |
| return NULL; |
| } |
| wcsncpy_s(fullPath, dirLen + 1, path, dirLen); |
| wcsncpy_s(fullPath + dirLen, nameLen + 1, lpData->cFileName, nameLen); |
| } |
| |
| // read symlink target attributes |
| HANDLE h = CreateFileW(fullPath, 0, FILE_SHARE_ATTRIBUTES, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); |
| if (h != INVALID_HANDLE_VALUE) { |
| BY_HANDLE_FILE_INFORMATION targetData; |
| if (GetFileInformationByHandle(h, &targetData)) { |
| attributes = targetData.dwFileAttributes | FILE_ATTRIBUTE_REPARSE_POINT; |
| timestamp = pairToInt64(targetData.ftLastWriteTime.dwLowDateTime, targetData.ftLastWriteTime.dwHighDateTime); |
| length = pairToInt64(targetData.nFileSizeLow, targetData.nFileSizeHigh); |
| } |
| CloseHandle(h); |
| } |
| |
| if (fullPath != path) { |
| free(fullPath); |
| } |
| } |
| else { |
| attributes &= (~ FILE_ATTRIBUTE_REPARSE_POINT); // keep reparse flag only for symlinks |
| } |
| } |
| |
| jobject o = env->AllocObject(aClass); |
| if (o == NULL) { |
| return NULL; |
| } |
| |
| jstring fileName = env->NewString((jchar*)lpData->cFileName, (jsize)wcslen(lpData->cFileName)); |
| if (fileName == NULL) { |
| return NULL; |
| } |
| |
| env->SetObjectField(o, nameID, fileName); |
| env->SetIntField(o, attributesID, attributes); |
| env->SetLongField(o, timestampID, timestamp); |
| env->SetLongField(o, lengthID, length); |
| |
| return o; |
| } |
| |
| static jobjectArray CopyObjectArray(JNIEnv* env, jobjectArray src, jclass aClass, jsize count, jsize newSize) { |
| jobjectArray dst = env->NewObjectArray(newSize, aClass, NULL); |
| if (dst != NULL) { |
| for (jsize i = 0; i < count; i++) { |
| jobject o = env->GetObjectArrayElement(src, i); |
| env->SetObjectArrayElement(dst, i, o); |
| env->DeleteLocalRef(o); |
| } |
| } |
| env->DeleteLocalRef(src); |
| return dst; |
| } |
| |
| static inline LONGLONG pairToInt64(DWORD lowPart, DWORD highPart) { |
| ULARGE_INTEGER large; |
| large.LowPart = lowPart; |
| large.HighPart = highPart; |
| return large.QuadPart; |
| } |