| /* | |
| * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * 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. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * 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. | |
| */ | |
| #include <assert.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <ctype.h> | |
| #include <direct.h> | |
| #include <windows.h> | |
| #include <io.h> | |
| #include <limits.h> | |
| #include <wchar.h> | |
| #include <Winioctl.h> | |
| #include "jni.h" | |
| #include "io_util.h" | |
| #include "jlong.h" | |
| #include "io_util_md.h" | |
| #include "java_io_FileSystem.h" | |
| #define MAX_PATH_LENGTH 1024 | |
| static struct { | |
| jfieldID path; | |
| } ids; | |
| JNIEXPORT void JNICALL | |
| Java_java_io_WinNTFileSystem_initIDs(JNIEnv *env, jclass cls) | |
| { | |
| jclass fileClass; | |
| fileClass = (*env)->FindClass(env, "java/io/File"); | |
| CHECK_NULL(fileClass); | |
| ids.path = (*env)->GetFieldID(env, fileClass, "path", "Ljava/lang/String;"); | |
| CHECK_NULL(ids.path); | |
| } | |
| /* -- Path operations -- */ | |
| extern int wcanonicalize(const WCHAR *path, WCHAR *out, int len); | |
| extern int wcanonicalizeWithPrefix(const WCHAR *canonicalPrefix, const WCHAR *pathWithCanonicalPrefix, WCHAR *out, int len); | |
| /** | |
| * Retrieves the fully resolved (final) path for the given path or NULL | |
| * if the function fails. | |
| */ | |
| static WCHAR* getFinalPath(JNIEnv *env, const WCHAR *path) | |
| { | |
| HANDLE h; | |
| WCHAR *result; | |
| DWORD error; | |
| h = CreateFileW(path, | |
| FILE_READ_ATTRIBUTES, | |
| FILE_SHARE_DELETE | | |
| FILE_SHARE_READ | FILE_SHARE_WRITE, | |
| NULL, | |
| OPEN_EXISTING, | |
| FILE_FLAG_BACKUP_SEMANTICS, | |
| NULL); | |
| if (h == INVALID_HANDLE_VALUE) | |
| return NULL; | |
| /** | |
| * Allocate a buffer for the resolved path. For a long path we may need | |
| * to allocate a larger buffer. | |
| */ | |
| result = (WCHAR*)malloc(MAX_PATH * sizeof(WCHAR)); | |
| if (result != NULL) { | |
| DWORD len = GetFinalPathNameByHandleW(h, result, MAX_PATH, 0); | |
| if (len >= MAX_PATH) { | |
| /* retry with a buffer of the right size */ | |
| WCHAR* newResult = (WCHAR*)realloc(result, (len+1) * sizeof(WCHAR)); | |
| if (newResult != NULL) { | |
| result = newResult; | |
| len = GetFinalPathNameByHandleW(h, result, len, 0); | |
| } else { | |
| len = 0; | |
| JNU_ThrowOutOfMemoryError(env, "native memory allocation failed"); | |
| } | |
| } | |
| if (len > 0) { | |
| /** | |
| * Strip prefix (should be \\?\ or \\?\UNC) | |
| */ | |
| if (result[0] == L'\\' && result[1] == L'\\' && | |
| result[2] == L'?' && result[3] == L'\\') | |
| { | |
| int isUnc = (result[4] == L'U' && | |
| result[5] == L'N' && | |
| result[6] == L'C'); | |
| int prefixLen = (isUnc) ? 7 : 4; | |
| int prefixToKeep = (isUnc) ? 1 : 0; | |
| // the amount to copy includes terminator | |
| int amountToCopy = len - prefixLen + 1; | |
| wmemmove(result + prefixToKeep, result + prefixLen, amountToCopy); | |
| } | |
| } | |
| /* unable to get final path */ | |
| if (len == 0 && result != NULL) { | |
| free(result); | |
| result = NULL; | |
| } | |
| } else { | |
| JNU_ThrowOutOfMemoryError(env, "native memory allocation failed"); | |
| } | |
| error = GetLastError(); | |
| if (CloseHandle(h)) | |
| SetLastError(error); | |
| return result; | |
| } | |
| /** | |
| * Retrieves file information for the specified file. If the file is | |
| * symbolic link then the information on fully resolved target is | |
| * returned. | |
| */ | |
| static BOOL getFileInformation(const WCHAR *path, | |
| BY_HANDLE_FILE_INFORMATION *finfo) | |
| { | |
| BOOL result; | |
| DWORD error; | |
| HANDLE h = CreateFileW(path, | |
| FILE_READ_ATTRIBUTES, | |
| FILE_SHARE_DELETE | | |
| FILE_SHARE_READ | FILE_SHARE_WRITE, | |
| NULL, | |
| OPEN_EXISTING, | |
| FILE_FLAG_BACKUP_SEMANTICS, | |
| NULL); | |
| if (h == INVALID_HANDLE_VALUE) | |
| return FALSE; | |
| result = GetFileInformationByHandle(h, finfo); | |
| error = GetLastError(); | |
| if (CloseHandle(h)) | |
| SetLastError(error); | |
| return result; | |
| } | |
| /** | |
| * path is likely to be a Unix domain socket. | |
| * Verify and if it is return its attributes | |
| */ | |
| static DWORD getFinalAttributesUnixSocket(const WCHAR *path) | |
| { | |
| DWORD result; | |
| BY_HANDLE_FILE_INFORMATION finfo; | |
| REPARSE_GUID_DATA_BUFFER reparse; | |
| HANDLE h = CreateFileW(path, | |
| FILE_READ_ATTRIBUTES, | |
| FILE_SHARE_DELETE | | |
| FILE_SHARE_READ | FILE_SHARE_WRITE, | |
| NULL, | |
| OPEN_EXISTING, | |
| FILE_FLAG_BACKUP_SEMANTICS | | |
| FILE_FLAG_OPEN_REPARSE_POINT, | |
| NULL); | |
| if (h == INVALID_HANDLE_VALUE) | |
| return INVALID_FILE_ATTRIBUTES; | |
| if (!GetFileInformationByHandle(h, &finfo)) { | |
| DWORD error = GetLastError(); | |
| if (CloseHandle(h)) { | |
| SetLastError(error); | |
| } | |
| return INVALID_FILE_ATTRIBUTES; | |
| } | |
| if ((finfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) { | |
| CloseHandle(h); | |
| return INVALID_FILE_ATTRIBUTES; | |
| } | |
| /* check the reparse tag */ | |
| if (DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, NULL, 0, &reparse, | |
| (DWORD)sizeof(reparse), &result, NULL) == 0) { | |
| CloseHandle(h); | |
| return INVALID_FILE_ATTRIBUTES; | |
| } | |
| if (reparse.ReparseTag != IO_REPARSE_TAG_AF_UNIX) { | |
| CloseHandle(h); | |
| return INVALID_FILE_ATTRIBUTES; | |
| } | |
| CloseHandle(h); | |
| return finfo.dwFileAttributes; | |
| } | |
| /** | |
| * If the given attributes are the attributes of a reparse point, then | |
| * read and return the attributes of the special cases. | |
| */ | |
| DWORD getFinalAttributesIfReparsePoint(WCHAR *path, DWORD a) | |
| { | |
| if ((a != INVALID_FILE_ATTRIBUTES) && | |
| ((a & FILE_ATTRIBUTE_REPARSE_POINT) != 0)) | |
| { | |
| BY_HANDLE_FILE_INFORMATION finfo; | |
| BOOL res = getFileInformation(path, &finfo); | |
| a = (res) ? finfo.dwFileAttributes : INVALID_FILE_ATTRIBUTES; | |
| } | |
| return a; | |
| } | |
| /** | |
| * Take special cases into account when retrieving the attributes | |
| * of path | |
| */ | |
| DWORD getFinalAttributes(WCHAR *path) | |
| { | |
| DWORD attr = INVALID_FILE_ATTRIBUTES; | |
| WIN32_FILE_ATTRIBUTE_DATA wfad; | |
| WIN32_FIND_DATAW wfd; | |
| HANDLE h; | |
| if (GetFileAttributesExW(path, GetFileExInfoStandard, &wfad)) { | |
| attr = getFinalAttributesIfReparsePoint(path, wfad.dwFileAttributes); | |
| if (attr == INVALID_FILE_ATTRIBUTES) { | |
| if (GetLastError() == ERROR_CANT_ACCESS_FILE) { | |
| attr = getFinalAttributesUnixSocket(path); | |
| } | |
| } | |
| } else { | |
| DWORD lerr = GetLastError(); | |
| if ((lerr == ERROR_SHARING_VIOLATION || lerr == ERROR_ACCESS_DENIED) && | |
| (h = FindFirstFileW(path, &wfd)) != INVALID_HANDLE_VALUE) { | |
| attr = getFinalAttributesIfReparsePoint(path, wfd.dwFileAttributes); | |
| FindClose(h); | |
| } | |
| } | |
| return attr; | |
| } | |
| JNIEXPORT jstring JNICALL | |
| Java_java_io_WinNTFileSystem_canonicalize0(JNIEnv *env, jobject this, | |
| jstring pathname) | |
| { | |
| jstring rv = NULL; | |
| WCHAR canonicalPath[MAX_PATH_LENGTH]; | |
| WITH_UNICODE_STRING(env, pathname, path) { | |
| /* we estimate the max length of memory needed as | |
| "currentDir. length + pathname.length" | |
| */ | |
| int len = (int)wcslen(path); | |
| len += currentDirLength(path, len); | |
| if (len > MAX_PATH_LENGTH - 1) { | |
| WCHAR *cp = (WCHAR*)malloc(len * sizeof(WCHAR)); | |
| if (cp != NULL) { | |
| if (wcanonicalize(path, cp, len) >= 0) { | |
| rv = (*env)->NewString(env, cp, (jsize)wcslen(cp)); | |
| } | |
| free(cp); | |
| } else { | |
| JNU_ThrowOutOfMemoryError(env, "native memory allocation failed"); | |
| } | |
| } else if (wcanonicalize(path, canonicalPath, MAX_PATH_LENGTH) >= 0) { | |
| rv = (*env)->NewString(env, canonicalPath, (jsize)wcslen(canonicalPath)); | |
| } | |
| } END_UNICODE_STRING(env, path); | |
| if (rv == NULL && !(*env)->ExceptionCheck(env)) { | |
| JNU_ThrowIOExceptionWithLastError(env, "Bad pathname"); | |
| } | |
| return rv; | |
| } | |
| JNIEXPORT jstring JNICALL | |
| Java_java_io_WinNTFileSystem_canonicalizeWithPrefix0(JNIEnv *env, jobject this, | |
| jstring canonicalPrefixString, | |
| jstring pathWithCanonicalPrefixString) | |
| { | |
| jstring rv = NULL; | |
| WCHAR canonicalPath[MAX_PATH_LENGTH]; | |
| WITH_UNICODE_STRING(env, canonicalPrefixString, canonicalPrefix) { | |
| WITH_UNICODE_STRING(env, pathWithCanonicalPrefixString, pathWithCanonicalPrefix) { | |
| int len = (int)wcslen(canonicalPrefix) + MAX_PATH; | |
| if (len > MAX_PATH_LENGTH) { | |
| WCHAR *cp = (WCHAR*)malloc(len * sizeof(WCHAR)); | |
| if (cp != NULL) { | |
| if (wcanonicalizeWithPrefix(canonicalPrefix, | |
| pathWithCanonicalPrefix, | |
| cp, len) >= 0) { | |
| rv = (*env)->NewString(env, cp, (jsize)wcslen(cp)); | |
| } | |
| free(cp); | |
| } else { | |
| JNU_ThrowOutOfMemoryError(env, "native memory allocation failed"); | |
| } | |
| } else if (wcanonicalizeWithPrefix(canonicalPrefix, | |
| pathWithCanonicalPrefix, | |
| canonicalPath, MAX_PATH_LENGTH) >= 0) { | |
| rv = (*env)->NewString(env, canonicalPath, (jsize)wcslen(canonicalPath)); | |
| } | |
| } END_UNICODE_STRING(env, pathWithCanonicalPrefix); | |
| } END_UNICODE_STRING(env, canonicalPrefix); | |
| if (rv == NULL && !(*env)->ExceptionCheck(env)) { | |
| JNU_ThrowIOExceptionWithLastError(env, "Bad pathname"); | |
| } | |
| return rv; | |
| } | |
| JNIEXPORT jstring JNICALL | |
| Java_java_io_WinNTFileSystem_getFinalPath0(JNIEnv* env, jobject this, jstring pathname) { | |
| jstring rv = NULL; | |
| WITH_UNICODE_STRING(env, pathname, path) { | |
| WCHAR* finalPath = getFinalPath(env, path); | |
| if (finalPath != NULL) { | |
| rv = (*env)->NewString(env, finalPath, (jsize)wcslen(finalPath)); | |
| free(finalPath); | |
| } | |
| } END_UNICODE_STRING(env, path); | |
| if (rv == NULL && !(*env)->ExceptionCheck(env)) { | |
| JNU_ThrowIOExceptionWithLastError(env, "Bad pathname"); | |
| } | |
| return rv; | |
| } | |
| /* -- Attribute accessors -- */ | |
| /* Check whether or not the file name in "path" is a Windows reserved | |
| device name (CON, PRN, AUX, NUL, COM[1-9], LPT[1-9]) based on the | |
| returned result from GetFullPathName, which should be in thr form of | |
| "\\.\[ReservedDeviceName]" if the path represents a reserved device | |
| name. | |
| Note1: GetFullPathName doesn't think "CLOCK$" (which is no longer | |
| important anyway) is a device name, so we don't check it here. | |
| GetFileAttributesEx will catch it later by returning 0 on NT/XP/ | |
| 200X. | |
| Note2: Theoretically the implementation could just lookup the table | |
| below linearly if the first 4 characters of the fullpath returned | |
| from GetFullPathName are "\\.\". The current implementation should | |
| achieve the same result. If Microsoft add more names into their | |
| reserved device name repository in the future, which probably will | |
| never happen, we will need to revisit the lookup implementation. | |
| static WCHAR* ReservedDEviceNames[] = { | |
| L"CON", L"PRN", L"AUX", L"NUL", | |
| L"COM1", L"COM2", L"COM3", L"COM4", L"COM5", L"COM6", L"COM7", L"COM8", L"COM9", | |
| L"LPT1", L"LPT2", L"LPT3", L"LPT4", L"LPT5", L"LPT6", L"LPT7", L"LPT8", L"LPT9", | |
| L"CLOCK$" | |
| }; | |
| */ | |
| static BOOL isReservedDeviceNameW(WCHAR* path) { | |
| #define BUFSIZE 9 | |
| WCHAR buf[BUFSIZE]; | |
| WCHAR *lpf = NULL; | |
| DWORD retLen = GetFullPathNameW(path, | |
| BUFSIZE, | |
| buf, | |
| &lpf); | |
| if ((retLen == BUFSIZE - 1 || retLen == BUFSIZE - 2) && | |
| buf[0] == L'\\' && buf[1] == L'\\' && | |
| buf[2] == L'.' && buf[3] == L'\\') { | |
| WCHAR* dname = _wcsupr(buf + 4); | |
| if (wcscmp(dname, L"CON") == 0 || | |
| wcscmp(dname, L"PRN") == 0 || | |
| wcscmp(dname, L"AUX") == 0 || | |
| wcscmp(dname, L"NUL") == 0) | |
| return TRUE; | |
| if ((wcsncmp(dname, L"COM", 3) == 0 || | |
| wcsncmp(dname, L"LPT", 3) == 0) && | |
| dname[3] - L'0' > 0 && | |
| dname[3] - L'0' <= 9) | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| JNIEXPORT jint JNICALL | |
| Java_java_io_WinNTFileSystem_getBooleanAttributes0(JNIEnv *env, jobject this, | |
| jobject file) | |
| { | |
| jint rv = 0; | |
| WCHAR *pathbuf = fileToNTPath(env, file, ids.path); | |
| if (pathbuf == NULL) | |
| return rv; | |
| if (!isReservedDeviceNameW(pathbuf)) { | |
| DWORD a = getFinalAttributes(pathbuf); | |
| if (a != INVALID_FILE_ATTRIBUTES) { | |
| rv = (java_io_FileSystem_BA_EXISTS | |
| | ((a & FILE_ATTRIBUTE_DIRECTORY) | |
| ? java_io_FileSystem_BA_DIRECTORY | |
| : java_io_FileSystem_BA_REGULAR) | |
| | ((a & FILE_ATTRIBUTE_HIDDEN) | |
| ? java_io_FileSystem_BA_HIDDEN : 0)); | |
| } | |
| } | |
| free(pathbuf); | |
| return rv; | |
| } | |
| JNIEXPORT jboolean | |
| JNICALL Java_java_io_WinNTFileSystem_checkAccess0(JNIEnv *env, jobject this, | |
| jobject file, jint access) | |
| { | |
| DWORD attr; | |
| WCHAR *pathbuf = fileToNTPath(env, file, ids.path); | |
| if (pathbuf == NULL) | |
| return JNI_FALSE; | |
| attr = GetFileAttributesW(pathbuf); | |
| attr = getFinalAttributesIfReparsePoint(pathbuf, attr); | |
| free(pathbuf); | |
| if (attr == INVALID_FILE_ATTRIBUTES) | |
| return JNI_FALSE; | |
| switch (access) { | |
| case java_io_FileSystem_ACCESS_READ: | |
| case java_io_FileSystem_ACCESS_EXECUTE: | |
| return JNI_TRUE; | |
| case java_io_FileSystem_ACCESS_WRITE: | |
| /* Read-only attribute ignored on directories */ | |
| if ((attr & FILE_ATTRIBUTE_DIRECTORY) || | |
| (attr & FILE_ATTRIBUTE_READONLY) == 0) | |
| return JNI_TRUE; | |
| else | |
| return JNI_FALSE; | |
| default: | |
| assert(0); | |
| return JNI_FALSE; | |
| } | |
| } | |
| JNIEXPORT jboolean JNICALL | |
| Java_java_io_WinNTFileSystem_setPermission0(JNIEnv *env, jobject this, | |
| jobject file, | |
| jint access, | |
| jboolean enable, | |
| jboolean owneronly) | |
| { | |
| jboolean rv = JNI_FALSE; | |
| WCHAR *pathbuf; | |
| DWORD a; | |
| if (access == java_io_FileSystem_ACCESS_READ || | |
| access == java_io_FileSystem_ACCESS_EXECUTE) { | |
| return enable; | |
| } | |
| pathbuf = fileToNTPath(env, file, ids.path); | |
| if (pathbuf == NULL) | |
| return JNI_FALSE; | |
| a = GetFileAttributesW(pathbuf); | |
| /* if reparse point, get final target */ | |
| if ((a != INVALID_FILE_ATTRIBUTES) && | |
| ((a & FILE_ATTRIBUTE_REPARSE_POINT) != 0)) | |
| { | |
| WCHAR *fp = getFinalPath(env, pathbuf); | |
| if (fp == NULL) { | |
| a = INVALID_FILE_ATTRIBUTES; | |
| } else { | |
| free(pathbuf); | |
| pathbuf = fp; | |
| a = GetFileAttributesW(pathbuf); | |
| } | |
| } | |
| if ((a != INVALID_FILE_ATTRIBUTES) && | |
| ((a & FILE_ATTRIBUTE_DIRECTORY) == 0)) | |
| { | |
| if (enable) | |
| a = a & ~FILE_ATTRIBUTE_READONLY; | |
| else | |
| a = a | FILE_ATTRIBUTE_READONLY; | |
| if (SetFileAttributesW(pathbuf, a)) | |
| rv = JNI_TRUE; | |
| } | |
| free(pathbuf); | |
| return rv; | |
| } | |
| JNIEXPORT jlong JNICALL | |
| Java_java_io_WinNTFileSystem_getLastModifiedTime0(JNIEnv *env, jobject this, | |
| jobject file) | |
| { | |
| jlong rv = 0; | |
| ULARGE_INTEGER modTime; | |
| FILETIME t; | |
| HANDLE h; | |
| WCHAR *pathbuf = fileToNTPath(env, file, ids.path); | |
| if (pathbuf == NULL) | |
| return rv; | |
| h = CreateFileW(pathbuf, | |
| /* Device query access */ | |
| 0, | |
| /* Share it */ | |
| FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, | |
| /* No security attributes */ | |
| NULL, | |
| /* Open existing or fail */ | |
| OPEN_EXISTING, | |
| /* Backup semantics for directories */ | |
| FILE_FLAG_BACKUP_SEMANTICS, | |
| /* No template file */ | |
| NULL); | |
| if (h != INVALID_HANDLE_VALUE) { | |
| if (GetFileTime(h, NULL, NULL, &t)) { | |
| modTime.LowPart = (DWORD) t.dwLowDateTime; | |
| modTime.HighPart = (LONG) t.dwHighDateTime; | |
| rv = modTime.QuadPart / 10000; | |
| rv -= 11644473600000; | |
| } | |
| CloseHandle(h); | |
| } | |
| free(pathbuf); | |
| return rv; | |
| } | |
| JNIEXPORT jlong JNICALL | |
| Java_java_io_WinNTFileSystem_getLength0(JNIEnv *env, jobject this, jobject file) | |
| { | |
| jlong rv = 0; | |
| WIN32_FILE_ATTRIBUTE_DATA wfad; | |
| WCHAR *pathbuf = fileToNTPath(env, file, ids.path); | |
| if (pathbuf == NULL) | |
| return rv; | |
| if (GetFileAttributesExW(pathbuf, | |
| GetFileExInfoStandard, | |
| &wfad)) { | |
| if ((wfad.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) { | |
| rv = wfad.nFileSizeHigh * ((jlong)MAXDWORD + 1) + wfad.nFileSizeLow; | |
| } else { | |
| /* file is a reparse point so read attributes of final target */ | |
| BY_HANDLE_FILE_INFORMATION finfo; | |
| if (getFileInformation(pathbuf, &finfo)) { | |
| rv = finfo.nFileSizeHigh * ((jlong)MAXDWORD + 1) + | |
| finfo.nFileSizeLow; | |
| } | |
| } | |
| } else { | |
| if (GetLastError() == ERROR_SHARING_VIOLATION) { | |
| // | |
| // The error is a "share violation", which means the file/dir | |
| // must exist. Try FindFirstFile, we know this at least works | |
| // for pagefile.sys. | |
| // | |
| WIN32_FIND_DATAW fileData; | |
| HANDLE h = FindFirstFileW(pathbuf, &fileData); | |
| if (h != INVALID_HANDLE_VALUE) { | |
| if ((fileData.dwFileAttributes & | |
| FILE_ATTRIBUTE_REPARSE_POINT) == 0) { | |
| WCHAR backslash = L'\\'; | |
| WCHAR *pslash = wcsrchr(pathbuf, backslash); | |
| if (pslash == NULL) { | |
| pslash = pathbuf; | |
| } else { | |
| pslash++; | |
| } | |
| WCHAR *fslash = wcsrchr(fileData.cFileName, backslash); | |
| if (fslash == NULL) { | |
| fslash = fileData.cFileName; | |
| } else { | |
| fslash++; | |
| } | |
| if (wcscmp(pslash, fslash) == 0) { | |
| ULARGE_INTEGER length; | |
| length.LowPart = fileData.nFileSizeLow; | |
| length.HighPart = fileData.nFileSizeHigh; | |
| if (length.QuadPart <= _I64_MAX) { | |
| rv = (jlong)length.QuadPart; | |
| } | |
| } | |
| } | |
| FindClose(h); | |
| } | |
| } | |
| } | |
| free(pathbuf); | |
| return rv; | |
| } | |
| /* -- File operations -- */ | |
| JNIEXPORT jboolean JNICALL | |
| Java_java_io_WinNTFileSystem_createFileExclusively0(JNIEnv *env, jclass cls, | |
| jstring path) | |
| { | |
| HANDLE h = NULL; | |
| WCHAR *pathbuf = pathToNTPath(env, path, JNI_FALSE); | |
| if (pathbuf == NULL) | |
| return JNI_FALSE; | |
| if (isReservedDeviceNameW(pathbuf)) { | |
| free(pathbuf); | |
| return JNI_FALSE; | |
| } | |
| h = CreateFileW( | |
| pathbuf, /* Wide char path name */ | |
| GENERIC_READ | GENERIC_WRITE, /* Read and write permission */ | |
| FILE_SHARE_READ | FILE_SHARE_WRITE, /* File sharing flags */ | |
| NULL, /* Security attributes */ | |
| CREATE_NEW, /* creation disposition */ | |
| FILE_ATTRIBUTE_NORMAL | | |
| FILE_FLAG_OPEN_REPARSE_POINT, /* flags and attributes */ | |
| NULL); | |
| if (h == INVALID_HANDLE_VALUE) { | |
| DWORD error = GetLastError(); | |
| if ((error != ERROR_FILE_EXISTS) && (error != ERROR_ALREADY_EXISTS)) { | |
| // return false rather than throwing an exception when there is | |
| // an existing file. | |
| DWORD a = GetFileAttributesW(pathbuf); | |
| if (a == INVALID_FILE_ATTRIBUTES) { | |
| SetLastError(error); | |
| JNU_ThrowIOExceptionWithLastError(env, "Could not open file"); | |
| } | |
| } | |
| free(pathbuf); | |
| return JNI_FALSE; | |
| } | |
| free(pathbuf); | |
| CloseHandle(h); | |
| return JNI_TRUE; | |
| } | |
| static int | |
| removeFileOrDirectory(const jchar *path, jboolean allowDeleteReadOnlyFiles) | |
| { | |
| /* Returns 0 on success */ | |
| DWORD a = GetFileAttributesW(path); | |
| if (a == INVALID_FILE_ATTRIBUTES) { | |
| return 1; | |
| } else if (a & FILE_ATTRIBUTE_DIRECTORY) { | |
| // read-only attribute cannot be set on directories | |
| return !RemoveDirectoryW(path); | |
| } else { | |
| // unset read-only attribute if deleting read-only files is enabled | |
| BOOL readOnlyAttrCleared = FALSE; | |
| if (allowDeleteReadOnlyFiles && ((a & FILE_ATTRIBUTE_READONLY) != 0)) { | |
| DWORD notReadOnlyAttr = a & (~FILE_ATTRIBUTE_READONLY); | |
| readOnlyAttrCleared = SetFileAttributesW(path, notReadOnlyAttr); | |
| } | |
| BOOL deleted = !DeleteFileW(path); | |
| // reinstate the read-only attribute if it was unset but deletion failed | |
| if (!deleted && readOnlyAttrCleared) | |
| SetFileAttributesW(path, a); | |
| return deleted; | |
| } | |
| } | |
| JNIEXPORT jboolean JNICALL | |
| Java_java_io_WinNTFileSystem_delete0(JNIEnv *env, jobject this, jobject file, | |
| jboolean allowDeleteReadOnlyFiles) | |
| { | |
| jboolean rv = JNI_FALSE; | |
| WCHAR *pathbuf = fileToNTPath(env, file, ids.path); | |
| if (pathbuf == NULL) { | |
| return JNI_FALSE; | |
| } | |
| if (removeFileOrDirectory(pathbuf, allowDeleteReadOnlyFiles) == 0) { | |
| rv = JNI_TRUE; | |
| } | |
| free(pathbuf); | |
| return rv; | |
| } | |
| JNIEXPORT jobjectArray JNICALL | |
| Java_java_io_WinNTFileSystem_list0(JNIEnv *env, jobject this, jobject file) | |
| { | |
| WCHAR *search_path; | |
| HANDLE handle; | |
| WIN32_FIND_DATAW find_data; | |
| int len, maxlen; | |
| jobjectArray rv, old; | |
| DWORD fattr; | |
| jstring name; | |
| jclass str_class; | |
| WCHAR *pathbuf; | |
| DWORD err; | |
| str_class = JNU_ClassString(env); | |
| CHECK_NULL_RETURN(str_class, NULL); | |
| pathbuf = fileToNTPath(env, file, ids.path); | |
| if (pathbuf == NULL) | |
| return NULL; | |
| search_path = (WCHAR*)malloc(2*wcslen(pathbuf) + 6); | |
| if (search_path == 0) { | |
| free (pathbuf); | |
| errno = ENOMEM; | |
| JNU_ThrowOutOfMemoryError(env, "native memory allocation failed"); | |
| return NULL; | |
| } | |
| wcscpy(search_path, pathbuf); | |
| free(pathbuf); | |
| fattr = GetFileAttributesW(search_path); | |
| if (fattr == INVALID_FILE_ATTRIBUTES) { | |
| free(search_path); | |
| return NULL; | |
| } else if ((fattr & FILE_ATTRIBUTE_DIRECTORY) == 0) { | |
| free(search_path); | |
| return NULL; | |
| } | |
| /* Remove trailing space chars from directory name */ | |
| len = (int)wcslen(search_path); | |
| while (search_path[len-1] == L' ') { | |
| len--; | |
| } | |
| search_path[len] = 0; | |
| /* Append "*", or possibly "\\*", to path */ | |
| if ((search_path[0] == L'\\' && search_path[1] == L'\0') || | |
| (search_path[1] == L':' | |
| && (search_path[2] == L'\0' | |
| || (search_path[2] == L'\\' && search_path[3] == L'\0')))) { | |
| /* No '\\' needed for cases like "\" or "Z:" or "Z:\" */ | |
| wcscat(search_path, L"*"); | |
| } else { | |
| wcscat(search_path, L"\\*"); | |
| } | |
| /* Open handle to the first file */ | |
| handle = FindFirstFileW(search_path, &find_data); | |
| free(search_path); | |
| if (handle == INVALID_HANDLE_VALUE) { | |
| if (GetLastError() != ERROR_FILE_NOT_FOUND) { | |
| // error | |
| return NULL; | |
| } else { | |
| // No files found - return an empty array | |
| rv = (*env)->NewObjectArray(env, 0, str_class, NULL); | |
| return rv; | |
| } | |
| } | |
| /* Allocate an initial String array */ | |
| len = 0; | |
| maxlen = 16; | |
| rv = (*env)->NewObjectArray(env, maxlen, str_class, NULL); | |
| if (rv == NULL) { // Couldn't allocate an array | |
| FindClose(handle); | |
| return NULL; | |
| } | |
| /* Scan the directory */ | |
| do { | |
| if (!wcscmp(find_data.cFileName, L".") | |
| || !wcscmp(find_data.cFileName, L"..")) | |
| continue; | |
| name = (*env)->NewString(env, find_data.cFileName, | |
| (jsize)wcslen(find_data.cFileName)); | |
| if (name == NULL) { | |
| FindClose(handle); | |
| return NULL; // error | |
| } | |
| if (len == maxlen) { | |
| old = rv; | |
| rv = (*env)->NewObjectArray(env, maxlen <<= 1, str_class, NULL); | |
| if (rv == NULL || JNU_CopyObjectArray(env, rv, old, len) < 0) { | |
| FindClose(handle); | |
| return NULL; // error | |
| } | |
| (*env)->DeleteLocalRef(env, old); | |
| } | |
| (*env)->SetObjectArrayElement(env, rv, len++, name); | |
| (*env)->DeleteLocalRef(env, name); | |
| } while (FindNextFileW(handle, &find_data)); | |
| err = GetLastError(); | |
| FindClose(handle); | |
| if (err != ERROR_NO_MORE_FILES) { | |
| return NULL; // error | |
| } | |
| if (len < maxlen) { | |
| /* Copy the final results into an appropriately-sized array */ | |
| old = rv; | |
| rv = (*env)->NewObjectArray(env, len, str_class, NULL); | |
| if (rv == NULL) | |
| return NULL; /* error */ | |
| if (JNU_CopyObjectArray(env, rv, old, len) < 0) | |
| return NULL; /* error */ | |
| } | |
| return rv; | |
| } | |
| JNIEXPORT jboolean JNICALL | |
| Java_java_io_WinNTFileSystem_createDirectory0(JNIEnv *env, jobject this, | |
| jobject file) | |
| { | |
| BOOL h = FALSE; | |
| WCHAR *pathbuf = fileToNTPath(env, file, ids.path); | |
| if (pathbuf == NULL) { | |
| /* Exception is pending */ | |
| return JNI_FALSE; | |
| } | |
| h = CreateDirectoryW(pathbuf, NULL); | |
| free(pathbuf); | |
| if (h == 0) { | |
| return JNI_FALSE; | |
| } | |
| return JNI_TRUE; | |
| } | |
| JNIEXPORT jboolean JNICALL | |
| Java_java_io_WinNTFileSystem_rename0(JNIEnv *env, jobject this, jobject from, | |
| jobject to) | |
| { | |
| jboolean rv = JNI_FALSE; | |
| WCHAR *frompath = fileToNTPath(env, from, ids.path); | |
| WCHAR *topath = fileToNTPath(env, to, ids.path); | |
| if (frompath != NULL && topath != NULL && _wrename(frompath, topath) == 0) { | |
| rv = JNI_TRUE; | |
| } | |
| free(frompath); | |
| free(topath); | |
| return rv; | |
| } | |
| JNIEXPORT jboolean JNICALL | |
| Java_java_io_WinNTFileSystem_setLastModifiedTime0(JNIEnv *env, jobject this, | |
| jobject file, jlong time) | |
| { | |
| jboolean rv = JNI_FALSE; | |
| WCHAR *pathbuf = fileToNTPath(env, file, ids.path); | |
| HANDLE h; | |
| if (pathbuf == NULL) | |
| return JNI_FALSE; | |
| h = CreateFileW(pathbuf, | |
| FILE_WRITE_ATTRIBUTES, | |
| FILE_SHARE_READ | FILE_SHARE_WRITE, | |
| NULL, | |
| OPEN_EXISTING, | |
| FILE_FLAG_BACKUP_SEMANTICS, | |
| 0); | |
| if (h != INVALID_HANDLE_VALUE) { | |
| ULARGE_INTEGER modTime; | |
| FILETIME t; | |
| modTime.QuadPart = (time + 11644473600000L) * 10000L; | |
| t.dwLowDateTime = (DWORD)modTime.LowPart; | |
| t.dwHighDateTime = (DWORD)modTime.HighPart; | |
| if (SetFileTime(h, NULL, NULL, &t)) { | |
| rv = JNI_TRUE; | |
| } | |
| CloseHandle(h); | |
| } | |
| free(pathbuf); | |
| return rv; | |
| } | |
| JNIEXPORT jboolean JNICALL | |
| Java_java_io_WinNTFileSystem_setReadOnly0(JNIEnv *env, jobject this, | |
| jobject file) | |
| { | |
| jboolean rv = JNI_FALSE; | |
| DWORD a; | |
| WCHAR *pathbuf = fileToNTPath(env, file, ids.path); | |
| if (pathbuf == NULL) | |
| return JNI_FALSE; | |
| a = GetFileAttributesW(pathbuf); | |
| /* if reparse point, get final target */ | |
| if ((a != INVALID_FILE_ATTRIBUTES) && | |
| ((a & FILE_ATTRIBUTE_REPARSE_POINT) != 0)) | |
| { | |
| WCHAR *fp = getFinalPath(env, pathbuf); | |
| if (fp == NULL) { | |
| a = INVALID_FILE_ATTRIBUTES; | |
| } else { | |
| free(pathbuf); | |
| pathbuf = fp; | |
| a = GetFileAttributesW(pathbuf); | |
| } | |
| } | |
| if ((a != INVALID_FILE_ATTRIBUTES) && | |
| ((a & FILE_ATTRIBUTE_DIRECTORY) == 0)) { | |
| if (SetFileAttributesW(pathbuf, a | FILE_ATTRIBUTE_READONLY)) | |
| rv = JNI_TRUE; | |
| } | |
| free(pathbuf); | |
| return rv; | |
| } | |
| /* -- Filesystem interface -- */ | |
| JNIEXPORT jobject JNICALL | |
| Java_java_io_WinNTFileSystem_getDriveDirectory(JNIEnv *env, jobject this, | |
| jint drive) | |
| { | |
| jstring ret = NULL; | |
| jchar *p = currentDir(drive); | |
| jchar *pf = p; | |
| if (p == NULL) return NULL; | |
| if (iswalpha(*p) && (p[1] == L':')) p += 2; | |
| ret = (*env)->NewString(env, p, (jsize)wcslen(p)); | |
| free (pf); | |
| return ret; | |
| } | |
| JNIEXPORT jint JNICALL | |
| Java_java_io_WinNTFileSystem_listRoots0(JNIEnv *env, jclass ignored) | |
| { | |
| return GetLogicalDrives(); | |
| } | |
| JNIEXPORT jlong JNICALL | |
| Java_java_io_WinNTFileSystem_getSpace0(JNIEnv *env, jobject this, | |
| jobject file, jint t) | |
| { | |
| WCHAR volname[MAX_PATH_LENGTH + 1]; | |
| jlong rv = 0L; | |
| WCHAR *pathbuf = fileToNTPath(env, file, ids.path); | |
| if (GetVolumePathNameW(pathbuf, volname, MAX_PATH_LENGTH)) { | |
| ULARGE_INTEGER totalSpace, freeSpace, usableSpace; | |
| if (GetDiskFreeSpaceExW(volname, &usableSpace, &totalSpace, &freeSpace)) { | |
| switch(t) { | |
| case java_io_FileSystem_SPACE_TOTAL: | |
| rv = long_to_jlong(totalSpace.QuadPart); | |
| break; | |
| case java_io_FileSystem_SPACE_FREE: | |
| rv = long_to_jlong(freeSpace.QuadPart); | |
| break; | |
| case java_io_FileSystem_SPACE_USABLE: | |
| rv = long_to_jlong(usableSpace.QuadPart); | |
| break; | |
| default: | |
| assert(0); | |
| } | |
| } | |
| } | |
| free(pathbuf); | |
| return rv; | |
| } | |
| // pathname is expected to be either null or to contain the root | |
| // of the path terminated by a backslash | |
| JNIEXPORT jint JNICALL | |
| Java_java_io_WinNTFileSystem_getNameMax0(JNIEnv *env, jobject this, | |
| jstring pathname) | |
| { | |
| BOOL res = 0; | |
| DWORD maxComponentLength; | |
| if (pathname == NULL) { | |
| res = GetVolumeInformationW(NULL, | |
| NULL, | |
| 0, | |
| NULL, | |
| &maxComponentLength, | |
| NULL, | |
| NULL, | |
| 0); | |
| } else { | |
| WITH_UNICODE_STRING(env, pathname, path) { | |
| res = GetVolumeInformationW(path, | |
| NULL, | |
| 0, | |
| NULL, | |
| &maxComponentLength, | |
| NULL, | |
| NULL, | |
| 0); | |
| } END_UNICODE_STRING(env, path); | |
| } | |
| if (res == 0) { | |
| JNU_ThrowIOExceptionWithLastError(env, | |
| "Could not get maximum component length"); | |
| } | |
| return (jint)maxComponentLength; | |
| } |