Skip to content

Commit 317d41b

Browse files
committed
[FS] Replace DosHandler by a Handler calling native functions via JNA
Replace the 'DosHandler' by the new 'Win32Handler' that calls the native Windows method 'FindFirstFileW() via to read file-info and 'SetFileAttributesW()' to set file attributes, similar to the JNI based 'LocalFileHandler', but only uses JNA to perform native calls. In the DosHandler calling 'Path.toRealPath()' is a very expensive operation on Windows because for each segment of the path the actual name on the file-system is looked up (the Windows FS is case-insensitive). In a Windows JDK this is implemented by calling the native Windows function 'FindFirstFileW()' for each segment of path. Because we are just interested in the last segment's name, i.e. the filename, all calls except for the last segment are unnecessary. With this change the 'DosHandler' is replaced by the optimized 'Win32Handler' that calls 'FindFirstFileW()' only once for the last segment of the path and to read all file-infos from the passed 'WIN32_FIND_DATAW' structure. Depending on the length of the path, this speeds up DosHandler.fetchFileInfo() between multiple factors up to a magnitude. Furthermore 'putFileInfo()' is optimized by replacing the code that formerly set file-attributes through a java.nio DosFileAttributeView by a single native call (through JNA) to 'GetFileAttributesW()' and 'SetFileAttributesW()'. The latter is only called if applying all attribute values to set changes the current values. Using the 'DosFileAttributeView' works similar, but reads and write the file-attributes individually for each attribute value that is set (at the moment three are set). Consequently the runtime of 'putFileInfo()' should be reduced by a factor of about three. The new implementation considers the behavior of the previous java.nio.file based implementation but also considers the current native/JNI implementation of 'LocalFileNatives'. It intended to have all the capabilities of the native/JNI implementation and its logic is based on that but performs all native calls through JNA.
1 parent 73319a5 commit 317d41b

File tree

5 files changed

+254
-115
lines changed

5 files changed

+254
-115
lines changed

resources/bundles/org.eclipse.core.filesystem/META-INF/MANIFEST.MF

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,5 @@ Bundle-Vendor: %providerName
1717
Bundle-RequiredExecutionEnvironment: JavaSE-17
1818
Bundle-ActivationPolicy: lazy
1919
Automatic-Module-Name: org.eclipse.core.filesystem
20+
Import-Package: com.sun.jna;version="[5.14.0,6.0.0)",
21+
com.sun.jna.platform.win32;version="[5.14.0,6.0.0)"

resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/Convert.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ public class Convert {
2525
/** Indicates if we are running on windows */
2626
private static final boolean IS_WINDOWS = Platform.OS.isWindows();
2727

28-
private static final String WIN32_FILE_PREFIX = "\\\\?\\"; //$NON-NLS-1$
29-
private static final String WIN32_UNC_FILE_PREFIX = "\\\\?\\UNC"; //$NON-NLS-1$
28+
public static final String WIN32_RAW_PATH_PREFIX = "\\\\?\\"; //$NON-NLS-1$
29+
public static final String WIN32_UNC_RAW_PATH_PREFIX = "\\\\?\\UNC"; //$NON-NLS-1$
3030

3131
/**
3232
* Calling new String(byte[] s) creates a new encoding object and other garbage.
@@ -68,7 +68,7 @@ public static byte[] toPlatformBytes(String target) {
6868

6969
/**
7070
* Converts a file name to a unicode char[] suitable for use by native methods.
71-
* See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp
71+
* See https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
7272
*/
7373
public static char[] toPlatformChars(String target) {
7474
//Windows use special prefix to handle long filenames
@@ -78,17 +78,17 @@ public static char[] toPlatformChars(String target) {
7878
//convert UNC path of form \\server\path to unicode form \\?\UNC\server\path
7979
if (target.startsWith("\\\\")) { //$NON-NLS-1$
8080
int nameLength = target.length();
81-
int prefixLength = WIN32_UNC_FILE_PREFIX.length();
81+
int prefixLength = WIN32_UNC_RAW_PATH_PREFIX.length();
8282
char[] result = new char[prefixLength + nameLength - 1];
83-
WIN32_UNC_FILE_PREFIX.getChars(0, prefixLength, result, 0);
83+
WIN32_UNC_RAW_PATH_PREFIX.getChars(0, prefixLength, result, 0);
8484
target.getChars(1, nameLength, result, prefixLength);
8585
return result;
8686
}
8787
//convert simple path of form c:\path to unicode form \\?\c:\path
8888
int nameLength = target.length();
89-
int prefixLength = WIN32_FILE_PREFIX.length();
89+
int prefixLength = WIN32_RAW_PATH_PREFIX.length();
9090
char[] result = new char[prefixLength + nameLength];
91-
WIN32_UNC_FILE_PREFIX.getChars(0, prefixLength, result, 0);
91+
WIN32_RAW_PATH_PREFIX.getChars(0, prefixLength, result, 0);
9292
target.getChars(0, nameLength, result, prefixLength);
9393
return result;
9494
}

resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/LocalFileNativesManager.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import org.eclipse.core.filesystem.IFileInfo;
2020
import org.eclipse.core.filesystem.provider.FileInfo;
2121
import org.eclipse.core.internal.filesystem.local.nio.DefaultHandler;
22-
import org.eclipse.core.internal.filesystem.local.nio.DosHandler;
2322
import org.eclipse.core.internal.filesystem.local.nio.PosixHandler;
2423
import org.eclipse.core.internal.filesystem.local.unix.UnixFileHandler;
2524
import org.eclipse.core.internal.filesystem.local.unix.UnixFileNatives;
@@ -73,7 +72,7 @@ public static boolean setUsingNative(boolean useNatives) {
7372
if (views.contains("posix")) { //$NON-NLS-1$
7473
HANDLER = new PosixHandler();
7574
} else if (views.contains("dos")) { //$NON-NLS-1$
76-
HANDLER = new DosHandler();
75+
HANDLER = new Win32Handler();
7776
} else {
7877
HANDLER = new DefaultHandler();
7978
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024, 2024 Hannes Wellmann and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Hannes Wellmann - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.core.internal.filesystem.local;
15+
16+
import static org.eclipse.core.internal.filesystem.local.Convert.WIN32_RAW_PATH_PREFIX;
17+
import static org.eclipse.core.internal.filesystem.local.Convert.WIN32_UNC_RAW_PATH_PREFIX;
18+
19+
import com.sun.jna.Memory;
20+
import com.sun.jna.Native;
21+
import com.sun.jna.NativeLibrary;
22+
import com.sun.jna.Pointer;
23+
import com.sun.jna.WString;
24+
import com.sun.jna.platform.win32.WinBase;
25+
import com.sun.jna.platform.win32.WinDef;
26+
import com.sun.jna.platform.win32.WinError;
27+
import com.sun.jna.platform.win32.WinNT;
28+
import java.io.IOException;
29+
import java.nio.file.Files;
30+
import java.nio.file.Path;
31+
import java.util.Date;
32+
import org.eclipse.core.filesystem.EFS;
33+
import org.eclipse.core.filesystem.IFileInfo;
34+
import org.eclipse.core.filesystem.provider.FileInfo;
35+
36+
/**
37+
* A NativeHandler for Windows file systems that supports legacy {@code DOS} attributes and
38+
* uses the Windows {@code fileapi.h} API called through JNA.
39+
*/
40+
public class Win32Handler extends NativeHandler {
41+
private static final int ATTRIBUTES = EFS.ATTRIBUTE_SYMLINK | EFS.ATTRIBUTE_LINK_TARGET // symbolic link support
42+
| EFS.ATTRIBUTE_ARCHIVE | EFS.ATTRIBUTE_READ_ONLY | EFS.ATTRIBUTE_HIDDEN; // standard DOS attributes
43+
44+
@Override
45+
public int getSupportedAttributes() {
46+
return ATTRIBUTES;
47+
}
48+
49+
@Override
50+
public FileInfo fetchFileInfo(String fileName) {
51+
FileInfo fileInfo = new FileInfo();
52+
53+
String target = toLongWindowsPath(fileName);
54+
55+
if (target.length() == 7 && target.startsWith(WIN32_RAW_PATH_PREFIX) && target.endsWith(":\\")) { //$NON-NLS-1$
56+
// FindFirstFile does not work at the root level. However, we don't need it because the root will never change time-stamp.
57+
// A root path is for example: \\?\c:\
58+
fileInfo.setDirectory(true);
59+
fileInfo.setExists(Files.exists(Path.of(target.substring(WIN32_RAW_PATH_PREFIX.length()))));
60+
return fileInfo;
61+
}
62+
63+
try (Memory mem = new Memory(WIN32_FIND_DATA_SIZE)) {
64+
// Allocating (uninitialized) memory explicitly in advance is faster than using
65+
// the slightly more convenient direct instantiation of a JNA WinBase.WIN32_FIND_DATA structure,
66+
// probably because the latter perform also an initial autowrite and computes the size each time.
67+
// For the same reason it is again faster to read the data-structure 'manually'.
68+
long handle = FileAPIh.FindFirstFileW(new WString(target), mem);
69+
if (handle == FileAPIh.INVALID_HANDLE_VALUE) {
70+
int error = Native.getLastError();
71+
if (error != WinError.ERROR_FILE_NOT_FOUND) {
72+
fileInfo.setError(IFileInfo.IO_ERROR);
73+
}
74+
return fileInfo;
75+
}
76+
FileAPIh.FindClose(handle);
77+
78+
convertFindDataWToFileInfo(mem, fileInfo, fileName);
79+
} catch (IOException e) {
80+
// Leave alone and continue. The name is set before an IOException can be thrown
81+
fileInfo.setError(IFileInfo.IO_ERROR);
82+
}
83+
return fileInfo;
84+
}
85+
86+
@Override
87+
public boolean putFileInfo(String fileName, IFileInfo info, int options) {
88+
WString lpFileName = new WString(toLongWindowsPath(fileName));
89+
long dwFileAttributes = FileAPIh.GetFileAttributesW(lpFileName);
90+
if (dwFileAttributes == FileAPIh.INVALID_FILE_ATTRIBUTES) {
91+
return false;
92+
}
93+
if (dwFileAttributes == WinNT.FILE_ATTRIBUTE_NORMAL) {
94+
// Assume nothing is set, as the documentation of FILE_ATTRIBUTE_NORMAL states:
95+
// "A file that does not have other attributes set. This attribute is valid only when used alone."
96+
dwFileAttributes = 0;
97+
}
98+
long fileAttributes = dwFileAttributes;
99+
100+
boolean archive = info.getAttribute(EFS.ATTRIBUTE_ARCHIVE);
101+
boolean readOnly = info.getAttribute(EFS.ATTRIBUTE_READ_ONLY);
102+
boolean hidden = info.getAttribute(EFS.ATTRIBUTE_HIDDEN);
103+
fileAttributes = set(fileAttributes, WinNT.FILE_ATTRIBUTE_ARCHIVE, archive);
104+
fileAttributes = set(fileAttributes, WinNT.FILE_ATTRIBUTE_READONLY, readOnly);
105+
fileAttributes = set(fileAttributes, WinNT.FILE_ATTRIBUTE_HIDDEN, hidden);
106+
107+
if (dwFileAttributes == fileAttributes) {
108+
return true; // Everything is already up to date -> nothing to do
109+
}
110+
return FileAPIh.SetFileAttributesW(lpFileName, fileAttributes);
111+
}
112+
113+
private static String toLongWindowsPath(String fileName) {
114+
// See https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
115+
if (fileName.startsWith("\\\\") && !fileName.startsWith(WIN32_UNC_RAW_PATH_PREFIX)) { //$NON-NLS-1$
116+
//convert UNC path of form \\server\path to long/unicode form \\?\UNC\server\path
117+
return WIN32_UNC_RAW_PATH_PREFIX + fileName.substring(1);
118+
} else if (!fileName.startsWith(WIN32_RAW_PATH_PREFIX)) {
119+
//convert simple path of form C:\path to long/unicode form \\?\C:\path
120+
return WIN32_RAW_PATH_PREFIX + fileName;
121+
}
122+
return fileName;
123+
}
124+
125+
static class FileAPIh {
126+
static {
127+
Native.register(NativeLibrary.getInstance("Kernel32" /* , W32APIOptions.DEFAULT_OPTIONS */ )); //$NON-NLS-1$
128+
// Not using W32APIOptions.DEFAULT_OPTIONS requires the usage of a few special types (e.g. WString) but improves performance.
129+
}
130+
private static final long INVALID_HANDLE_VALUE = Pointer.nativeValue(WinBase.INVALID_HANDLE_VALUE.getPointer());
131+
132+
// winnt.h HANDLE can be expressed as java long
133+
134+
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfilew
135+
static native long FindFirstFileW(WString lpFileName, Pointer lpFindFileData);
136+
137+
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileattributesw
138+
static native long GetFileAttributesW(WString lpFileName);
139+
140+
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesw
141+
static native boolean SetFileAttributesW(WString lpFileName, long dwFileAttributes);
142+
143+
static final long INVALID_FILE_ATTRIBUTES = new WinBase.DWORD(-1).longValue();
144+
145+
static native boolean FindClose(long handle);
146+
}
147+
148+
private static final int DWORD_SIZE = WinBase.DWORD.SIZE;
149+
private static final int FILETIME_SIZE = 2 * DWORD_SIZE;
150+
private static final int WCHAR_SIZE = 2;
151+
152+
/**
153+
* Read the of the native memory data-strcuture
154+
* <a href="https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw">{@code WIN32_FIND_DATAW}</a>.
155+
* <pre>
156+
* typedef struct _WIN32_FIND_DATAW {
157+
* DWORD dwFileAttributes;
158+
* FILETIME ftCreationTime;
159+
* FILETIME ftLastAccessTime;
160+
* FILETIME ftLastWriteTime;
161+
* DWORD nFileSizeHigh;
162+
* DWORD nFileSizeLow;
163+
* DWORD dwReserved0;
164+
* DWORD dwReserved1;
165+
* _Field_z_ WCHAR cFileName[ MAX_PATH ];
166+
* _Field_z_ WCHAR cAlternateFileName[ 14 ];
167+
* } WIN32_FIND_DATAW
168+
* </pre>
169+
*/
170+
private static final int DW_FILE_ATTRIBUTES = 0;
171+
private static final int FT_CREATION_TIME = DW_FILE_ATTRIBUTES + DWORD_SIZE;
172+
private static final int FT_LAST_ACCESS_TIME = FT_CREATION_TIME + FILETIME_SIZE;
173+
private static final int FT_LAST_WRITE_TIME = FT_LAST_ACCESS_TIME + FILETIME_SIZE;
174+
private static final int N_FILE_SIZE_HIGH = FT_LAST_WRITE_TIME + FILETIME_SIZE;
175+
private static final int N_FILE_SIZE_LOW = N_FILE_SIZE_HIGH + DWORD_SIZE;
176+
private static final int DW_RESERVED_0 = N_FILE_SIZE_LOW + DWORD_SIZE;
177+
private static final int DW_RESERVED_1 = DW_RESERVED_0 + DWORD_SIZE;
178+
private static final int C_FILE_NAME = DW_RESERVED_1 + DWORD_SIZE;
179+
private static final int C_ALTERNATE_FILE_NAME = C_FILE_NAME + WinDef.MAX_PATH * WCHAR_SIZE;
180+
181+
private static final int WIN32_FIND_DATA_SIZE = C_ALTERNATE_FILE_NAME + 14 * WCHAR_SIZE;
182+
static {
183+
if (WIN32_FIND_DATA_SIZE != WinBase.WIN32_FIND_DATA.sizeOf()) {
184+
throw new IllegalStateException("Struct 'WIN32_FIND_DATAW' has unexpected size"); //$NON-NLS-1$
185+
}
186+
}
187+
private static final long MAXDWORD = 0xFFFFFFFFL; // unsigned long from winnt.h. On Windows a C long usually has only 32bit
188+
189+
private static void convertFindDataWToFileInfo(Memory mem, FileInfo info, String fileName) throws IOException {
190+
/**
191+
* For possible values of dwFileAttributes and their descriptions,
192+
* see <a href="https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants">File Attribute Constants</a>.
193+
*/
194+
int dwFileAttributes = readDWORDAsSignedInt(mem, DW_FILE_ATTRIBUTES);
195+
Date ftLastWriteTime = readFILETIME(mem, FT_LAST_WRITE_TIME);
196+
long nFileSizeHigh = readDWORD(mem, N_FILE_SIZE_HIGH);
197+
long nFileSizeLow = readDWORD(mem, N_FILE_SIZE_LOW);
198+
int dwReserved0 = readDWORDAsSignedInt(mem, DW_RESERVED_0);
199+
String cFileName = mem.getWideString(C_FILE_NAME);
200+
201+
long fileLength = (nFileSizeHigh * (MAXDWORD + 1)) + nFileSizeLow;
202+
203+
info.setName(cFileName);
204+
info.setExists(true);
205+
info.setLastModified(ftLastWriteTime.getTime());
206+
info.setLength(fileLength);
207+
info.setDirectory(isSet(dwFileAttributes, WinNT.FILE_ATTRIBUTE_DIRECTORY));
208+
info.setAttribute(EFS.ATTRIBUTE_ARCHIVE, isSet(dwFileAttributes, WinNT.FILE_ATTRIBUTE_ARCHIVE));
209+
info.setAttribute(EFS.ATTRIBUTE_READ_ONLY, isSet(dwFileAttributes, WinNT.FILE_ATTRIBUTE_READONLY));
210+
info.setAttribute(EFS.ATTRIBUTE_HIDDEN, isSet(dwFileAttributes, WinNT.FILE_ATTRIBUTE_HIDDEN));
211+
212+
boolean isReparsePoint = isSet(dwFileAttributes, WinNT.FILE_ATTRIBUTE_REPARSE_POINT);
213+
if (isReparsePoint && dwReserved0 == WinNT.IO_REPARSE_TAG_SYMLINK) {
214+
Path linkTarget = Files.readSymbolicLink(Path.of(fileName));
215+
info.setAttribute(EFS.ATTRIBUTE_SYMLINK, true);
216+
info.setStringAttribute(EFS.ATTRIBUTE_LINK_TARGET, linkTarget.toString());
217+
}
218+
}
219+
220+
private static int readDWORDAsSignedInt(Memory memory, int offset) {
221+
return memory.getInt(offset); // int is signed
222+
}
223+
224+
private static long readDWORD(Memory memory, int offset) {
225+
// From https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
226+
// "DWORD - A 32-bit unsigned integer. The range is 0 through 4294967295 decimal. This type is declared in IntSafe.h as follows: typedef unsigned long DWORD;"
227+
return readDWORDAsSignedInt(memory, offset) & MAXDWORD;
228+
}
229+
230+
private static Date readFILETIME(Memory memory, int offset) {
231+
int low = readDWORDAsSignedInt(memory, offset);
232+
int high = readDWORDAsSignedInt(memory, offset + DWORD_SIZE);
233+
return WinBase.FILETIME.filetimeToDate(high, low);
234+
}
235+
236+
private static boolean isSet(long field, int bit) {
237+
return (field & bit) != 0;
238+
}
239+
240+
private long set(long field, int bit, boolean isSet) {
241+
return isSet ? (field | bit) : (field & ~bit);
242+
}
243+
244+
}

0 commit comments

Comments
 (0)