From f07b368c0428b8f06e054c13061f21bb8eb16e94 Mon Sep 17 00:00:00 2001 From: Hannes Wellmann Date: Sat, 7 Mar 2026 15:06:55 +0100 Subject: [PATCH] Migrate Win32Handler to Java's new Foreign Function & Memory API This allows to drop the dependency on JNA and requires to update the required EE of 'org.eclipse.core.filesystem' to Java-25. Use the jextract tool provided by the OpenJDK project to generate the FFM bindings for the called methods of the Windows API. Add a script to regenerate these bindings. --- .../org.eclipse.core.filesystem/.classpath | 7 +- .../.settings/org.eclipse.jdt.core.prefs | 10 +- .../.settings/org.eclipse.jdt.ui.prefs | 2 +- .../META-INF/MANIFEST.MF | 6 +- .../build.properties | 3 +- .../generateWindowsH.bat | 32 + .../com/microsoft/windows/FILETIME.java | 29 + .../com/microsoft/windows/FileAPI$shared.java | 64 ++ .../com/microsoft/windows/FileAPI.java | 431 +++++++++++++ .../microsoft/windows/WIN32_FIND_DATAW.java | 37 ++ .../com/microsoft/windows/_FILETIME.java | 173 +++++ .../microsoft/windows/_WIN32_FIND_DATAW.java | 607 ++++++++++++++++++ .../filesystem/local/Win32Handler.java | 306 +++++---- 13 files changed, 1553 insertions(+), 154 deletions(-) create mode 100644 resources/bundles/org.eclipse.core.filesystem/generateWindowsH.bat create mode 100644 resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/FILETIME.java create mode 100644 resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/FileAPI$shared.java create mode 100644 resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/FileAPI.java create mode 100644 resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/WIN32_FIND_DATAW.java create mode 100644 resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/_FILETIME.java create mode 100644 resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/_WIN32_FIND_DATAW.java diff --git a/resources/bundles/org.eclipse.core.filesystem/.classpath b/resources/bundles/org.eclipse.core.filesystem/.classpath index 81fe078c20c..cc5bfc7a408 100644 --- a/resources/bundles/org.eclipse.core.filesystem/.classpath +++ b/resources/bundles/org.eclipse.core.filesystem/.classpath @@ -1,7 +1,12 @@ - + + + + + + diff --git a/resources/bundles/org.eclipse.core.filesystem/.settings/org.eclipse.jdt.core.prefs b/resources/bundles/org.eclipse.core.filesystem/.settings/org.eclipse.jdt.core.prefs index 3502cb6bd9f..d424094b9e5 100644 --- a/resources/bundles/org.eclipse.core.filesystem/.settings/org.eclipse.jdt.core.prefs +++ b/resources/bundles/org.eclipse.core.filesystem/.settings/org.eclipse.jdt.core.prefs @@ -17,9 +17,9 @@ org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nul org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=25 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.compliance=25 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -73,7 +73,7 @@ org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning -org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=error +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=warning org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error org.eclipse.jdt.core.compiler.problem.nullReference=warning @@ -112,7 +112,7 @@ org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExcepti org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=enabled org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore -org.eclipse.jdt.core.compiler.problem.unusedImport=error +org.eclipse.jdt.core.compiler.problem.unusedImport=warning org.eclipse.jdt.core.compiler.problem.unusedLabel=warning org.eclipse.jdt.core.compiler.problem.unusedLocal=error org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore @@ -125,7 +125,7 @@ org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.release=enabled -org.eclipse.jdt.core.compiler.source=17 +org.eclipse.jdt.core.compiler.source=25 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 diff --git a/resources/bundles/org.eclipse.core.filesystem/.settings/org.eclipse.jdt.ui.prefs b/resources/bundles/org.eclipse.core.filesystem/.settings/org.eclipse.jdt.ui.prefs index 0667cbf43e4..37c14010e10 100644 --- a/resources/bundles/org.eclipse.core.filesystem/.settings/org.eclipse.jdt.ui.prefs +++ b/resources/bundles/org.eclipse.core.filesystem/.settings/org.eclipse.jdt.ui.prefs @@ -85,7 +85,7 @@ sp_cleanup.pull_up_assignment=false sp_cleanup.push_down_negation=false sp_cleanup.qualify_static_field_accesses_with_declaring_class=false sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true -sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=false sp_cleanup.qualify_static_member_accesses_with_declaring_class=true sp_cleanup.qualify_static_method_accesses_with_declaring_class=false sp_cleanup.reduce_indentation=false diff --git a/resources/bundles/org.eclipse.core.filesystem/META-INF/MANIFEST.MF b/resources/bundles/org.eclipse.core.filesystem/META-INF/MANIFEST.MF index 46aaaccaef0..070532a6598 100644 --- a/resources/bundles/org.eclipse.core.filesystem/META-INF/MANIFEST.MF +++ b/resources/bundles/org.eclipse.core.filesystem/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.core.filesystem; singleton:=true -Bundle-Version: 1.11.600.qualifier +Bundle-Version: 1.12.0.qualifier Bundle-Localization: plugin Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)" Export-Package: org.eclipse.core.filesystem;uses:="org.eclipse.core.runtime", @@ -11,8 +11,6 @@ Export-Package: org.eclipse.core.filesystem;uses:="org.eclipse.core.runtime", org.eclipse.core.internal.filesystem.local;x-internal:=true, org.eclipse.core.internal.filesystem.local.unix;x-internal:=true Bundle-Vendor: %providerName -Bundle-RequiredExecutionEnvironment: JavaSE-17 +Bundle-RequiredExecutionEnvironment: JavaSE-25 Bundle-ActivationPolicy: lazy Automatic-Module-Name: org.eclipse.core.filesystem -Import-Package: com.sun.jna;version="[5.14.0,6.0.0)", - com.sun.jna.platform.win32;version="[5.14.0,6.0.0)" diff --git a/resources/bundles/org.eclipse.core.filesystem/build.properties b/resources/bundles/org.eclipse.core.filesystem/build.properties index a8d6005911d..d0c65665e21 100644 --- a/resources/bundles/org.eclipse.core.filesystem/build.properties +++ b/resources/bundles/org.eclipse.core.filesystem/build.properties @@ -11,7 +11,8 @@ # Contributors: # IBM Corporation - initial API and implementation ############################################################################### -source.. = src/ +source.. = src/,\ + src-gen/ output.. = bin/ bin.includes = META-INF/,\ .,\ diff --git a/resources/bundles/org.eclipse.core.filesystem/generateWindowsH.bat b/resources/bundles/org.eclipse.core.filesystem/generateWindowsH.bat new file mode 100644 index 00000000000..ecdd3ba7f75 --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/generateWindowsH.bat @@ -0,0 +1,32 @@ + +set "MSVC_HOME=C:\Program Files\Microsoft Visual Studio\2022\Community" +call "%MSVC_HOME%\VC\Auxiliary\Build\vcvarsall.bat" x64 + +@echo on + +jextract --output src-gen ^ + --include-function GetShortPathNameW ^ + --include-function GetFileAttributesW ^ + --include-function SetFileAttributesW ^ + --include-function FindFirstFileW ^ + --include-function FindClose ^ + --include-typedef WIN32_FIND_DATAW ^ + --include-struct _WIN32_FIND_DATAW ^ + --include-typedef FILETIME ^ + --include-struct _FILETIME ^ + --include-constant FILE_ATTRIBUTE_NORMAL ^ + --include-constant FILE_ATTRIBUTE_ARCHIVE ^ + --include-constant FILE_ATTRIBUTE_READONLY ^ + --include-constant FILE_ATTRIBUTE_HIDDEN ^ + --include-constant FILE_ATTRIBUTE_DIRECTORY ^ + --include-constant FILE_ATTRIBUTE_REPARSE_POINT ^ + --include-constant IO_REPARSE_TAG_SYMLINK ^ + --include-constant INVALID_FILE_ATTRIBUTES ^ + --include-constant INVALID_HANDLE_VALUE ^ + --include-constant ERROR_FILE_NOT_FOUND ^ + --include-constant ERROR_PATH_NOT_FOUND ^ + --target-package com.microsoft.windows ^ + --header-class-name FileAPI ^ + --library kernel32 ^ + --include-dir "%INCLUDE%" ^ + "Windows.h" diff --git a/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/FILETIME.java b/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/FILETIME.java new file mode 100644 index 00000000000..e54579249c8 --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/FILETIME.java @@ -0,0 +1,29 @@ +// Generated by jextract + +package com.microsoft.windows; + +import java.lang.invoke.*; +import java.lang.foreign.*; +import java.nio.ByteOrder; +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +import static java.lang.foreign.ValueLayout.*; +import static java.lang.foreign.MemoryLayout.PathElement.*; + +/** + * {@snippet lang=c : + * typedef struct _FILETIME { + * DWORD dwLowDateTime; + * DWORD dwHighDateTime; + * } FILETIME + * } + */ +public class FILETIME extends _FILETIME { + + FILETIME() { + // Should not be called directly + } +} + diff --git a/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/FileAPI$shared.java b/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/FileAPI$shared.java new file mode 100644 index 00000000000..78745b3b5a8 --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/FileAPI$shared.java @@ -0,0 +1,64 @@ +// Generated by jextract + +package com.microsoft.windows; + +import java.lang.invoke.*; +import java.lang.foreign.*; +import java.nio.ByteOrder; +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +import static java.lang.foreign.ValueLayout.*; +import static java.lang.foreign.MemoryLayout.PathElement.*; + +public class FileAPI$shared { + + FileAPI$shared() { + // Should not be called directly + } + + public static final ValueLayout.OfBoolean C_BOOL = (ValueLayout.OfBoolean) Linker.nativeLinker().canonicalLayouts().get("bool"); + public static final ValueLayout.OfByte C_CHAR =(ValueLayout.OfByte)Linker.nativeLinker().canonicalLayouts().get("char"); + public static final ValueLayout.OfShort C_SHORT = (ValueLayout.OfShort) Linker.nativeLinker().canonicalLayouts().get("short"); + public static final ValueLayout.OfInt C_INT = (ValueLayout.OfInt) Linker.nativeLinker().canonicalLayouts().get("int"); + public static final ValueLayout.OfLong C_LONG_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long long"); + public static final ValueLayout.OfFloat C_FLOAT = (ValueLayout.OfFloat) Linker.nativeLinker().canonicalLayouts().get("float"); + public static final ValueLayout.OfDouble C_DOUBLE = (ValueLayout.OfDouble) Linker.nativeLinker().canonicalLayouts().get("double"); + public static final AddressLayout C_POINTER = ((AddressLayout) Linker.nativeLinker().canonicalLayouts().get("void*")) + .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, C_CHAR)); + public static final ValueLayout.OfInt C_LONG = (ValueLayout.OfInt) Linker.nativeLinker().canonicalLayouts().get("long"); + public static final ValueLayout.OfDouble C_LONG_DOUBLE = (ValueLayout.OfDouble) Linker.nativeLinker().canonicalLayouts().get("double"); + + static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls"); + + static void traceDowncall(String name, Object... args) { + String traceArgs = Arrays.stream(args) + .map(Object::toString) + .collect(Collectors.joining(", ")); + System.out.printf("%s(%s)\n", name, traceArgs); + } + + static MethodHandle upcallHandle(Class fi, String name, FunctionDescriptor fdesc) { + try { + return MethodHandles.lookup().findVirtual(fi, name, fdesc.toMethodType()); + } catch (ReflectiveOperationException ex) { + throw new AssertionError(ex); + } + } + + static MemoryLayout align(MemoryLayout layout, long align) { + return switch (layout) { + case PaddingLayout p -> p; + case ValueLayout v -> v.withByteAlignment(align); + case GroupLayout g -> { + MemoryLayout[] alignedMembers = g.memberLayouts().stream() + .map(m -> align(m, align)).toArray(MemoryLayout[]::new); + yield g instanceof StructLayout ? + MemoryLayout.structLayout(alignedMembers) : MemoryLayout.unionLayout(alignedMembers); + } + case SequenceLayout s -> MemoryLayout.sequenceLayout(s.elementCount(), align(s.elementLayout(), align)); + }; + } +} + diff --git a/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/FileAPI.java b/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/FileAPI.java new file mode 100644 index 00000000000..e9db50333ba --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/FileAPI.java @@ -0,0 +1,431 @@ +// Generated by jextract + +package com.microsoft.windows; + +import java.lang.invoke.*; +import java.lang.foreign.*; +import java.nio.ByteOrder; +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +import static java.lang.foreign.ValueLayout.*; +import static java.lang.foreign.MemoryLayout.PathElement.*; + +public class FileAPI extends FileAPI$shared { + + FileAPI() { + // Should not be called directly + } + + static final Arena LIBRARY_ARENA = Arena.ofAuto(); + + static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.libraryLookup(System.mapLibraryName("kernel32"), LIBRARY_ARENA) + .or(SymbolLookup.loaderLookup()) + .or(Linker.nativeLinker().defaultLookup()); + + private static final int FILE_ATTRIBUTE_READONLY = (int)1L; + /** + * {@snippet lang=c : + * #define FILE_ATTRIBUTE_READONLY 1 + * } + */ + public static int FILE_ATTRIBUTE_READONLY() { + return FILE_ATTRIBUTE_READONLY; + } + private static final int FILE_ATTRIBUTE_HIDDEN = (int)2L; + /** + * {@snippet lang=c : + * #define FILE_ATTRIBUTE_HIDDEN 2 + * } + */ + public static int FILE_ATTRIBUTE_HIDDEN() { + return FILE_ATTRIBUTE_HIDDEN; + } + private static final int FILE_ATTRIBUTE_DIRECTORY = (int)16L; + /** + * {@snippet lang=c : + * #define FILE_ATTRIBUTE_DIRECTORY 16 + * } + */ + public static int FILE_ATTRIBUTE_DIRECTORY() { + return FILE_ATTRIBUTE_DIRECTORY; + } + private static final int FILE_ATTRIBUTE_ARCHIVE = (int)32L; + /** + * {@snippet lang=c : + * #define FILE_ATTRIBUTE_ARCHIVE 32 + * } + */ + public static int FILE_ATTRIBUTE_ARCHIVE() { + return FILE_ATTRIBUTE_ARCHIVE; + } + private static final int FILE_ATTRIBUTE_NORMAL = (int)128L; + /** + * {@snippet lang=c : + * #define FILE_ATTRIBUTE_NORMAL 128 + * } + */ + public static int FILE_ATTRIBUTE_NORMAL() { + return FILE_ATTRIBUTE_NORMAL; + } + private static final int FILE_ATTRIBUTE_REPARSE_POINT = (int)1024L; + /** + * {@snippet lang=c : + * #define FILE_ATTRIBUTE_REPARSE_POINT 1024 + * } + */ + public static int FILE_ATTRIBUTE_REPARSE_POINT() { + return FILE_ATTRIBUTE_REPARSE_POINT; + } + + private static class FindClose { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + FileAPI.C_INT, + FileAPI.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("FindClose"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * BOOL FindClose(HANDLE hFindFile) + * } + */ + public static FunctionDescriptor FindClose$descriptor() { + return FindClose.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * BOOL FindClose(HANDLE hFindFile) + * } + */ + public static MethodHandle FindClose$handle() { + return FindClose.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * BOOL FindClose(HANDLE hFindFile) + * } + */ + public static MemorySegment FindClose$address() { + return FindClose.ADDR; + } + + /** + * {@snippet lang=c : + * BOOL FindClose(HANDLE hFindFile) + * } + */ + public static int FindClose(MemorySegment hFindFile) { + var mh$ = FindClose.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("FindClose", hFindFile); + } + return (int)mh$.invokeExact(hFindFile); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class FindFirstFileW { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + FileAPI.C_POINTER, + FileAPI.C_POINTER, + FileAPI.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("FindFirstFileW"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * HANDLE FindFirstFileW(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData) + * } + */ + public static FunctionDescriptor FindFirstFileW$descriptor() { + return FindFirstFileW.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * HANDLE FindFirstFileW(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData) + * } + */ + public static MethodHandle FindFirstFileW$handle() { + return FindFirstFileW.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * HANDLE FindFirstFileW(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData) + * } + */ + public static MemorySegment FindFirstFileW$address() { + return FindFirstFileW.ADDR; + } + + /** + * {@snippet lang=c : + * HANDLE FindFirstFileW(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData) + * } + */ + public static MemorySegment FindFirstFileW(MemorySegment lpFileName, MemorySegment lpFindFileData) { + var mh$ = FindFirstFileW.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("FindFirstFileW", lpFileName, lpFindFileData); + } + return (MemorySegment)mh$.invokeExact(lpFileName, lpFindFileData); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class GetFileAttributesW { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + FileAPI.C_LONG, + FileAPI.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("GetFileAttributesW"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * DWORD GetFileAttributesW(LPCWSTR lpFileName) + * } + */ + public static FunctionDescriptor GetFileAttributesW$descriptor() { + return GetFileAttributesW.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * DWORD GetFileAttributesW(LPCWSTR lpFileName) + * } + */ + public static MethodHandle GetFileAttributesW$handle() { + return GetFileAttributesW.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * DWORD GetFileAttributesW(LPCWSTR lpFileName) + * } + */ + public static MemorySegment GetFileAttributesW$address() { + return GetFileAttributesW.ADDR; + } + + /** + * {@snippet lang=c : + * DWORD GetFileAttributesW(LPCWSTR lpFileName) + * } + */ + public static int GetFileAttributesW(MemorySegment lpFileName) { + var mh$ = GetFileAttributesW.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("GetFileAttributesW", lpFileName); + } + return (int)mh$.invokeExact(lpFileName); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class GetShortPathNameW { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + FileAPI.C_LONG, + FileAPI.C_POINTER, + FileAPI.C_POINTER, + FileAPI.C_LONG + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("GetShortPathNameW"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * DWORD GetShortPathNameW(LPCWSTR lpszLongPath, LPWSTR lpszShortPath, DWORD cchBuffer) + * } + */ + public static FunctionDescriptor GetShortPathNameW$descriptor() { + return GetShortPathNameW.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * DWORD GetShortPathNameW(LPCWSTR lpszLongPath, LPWSTR lpszShortPath, DWORD cchBuffer) + * } + */ + public static MethodHandle GetShortPathNameW$handle() { + return GetShortPathNameW.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * DWORD GetShortPathNameW(LPCWSTR lpszLongPath, LPWSTR lpszShortPath, DWORD cchBuffer) + * } + */ + public static MemorySegment GetShortPathNameW$address() { + return GetShortPathNameW.ADDR; + } + + /** + * {@snippet lang=c : + * DWORD GetShortPathNameW(LPCWSTR lpszLongPath, LPWSTR lpszShortPath, DWORD cchBuffer) + * } + */ + public static int GetShortPathNameW(MemorySegment lpszLongPath, MemorySegment lpszShortPath, int cchBuffer) { + var mh$ = GetShortPathNameW.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("GetShortPathNameW", lpszLongPath, lpszShortPath, cchBuffer); + } + return (int)mh$.invokeExact(lpszLongPath, lpszShortPath, cchBuffer); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class SetFileAttributesW { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + FileAPI.C_INT, + FileAPI.C_POINTER, + FileAPI.C_LONG + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("SetFileAttributesW"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * BOOL SetFileAttributesW(LPCWSTR lpFileName, DWORD dwFileAttributes) + * } + */ + public static FunctionDescriptor SetFileAttributesW$descriptor() { + return SetFileAttributesW.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * BOOL SetFileAttributesW(LPCWSTR lpFileName, DWORD dwFileAttributes) + * } + */ + public static MethodHandle SetFileAttributesW$handle() { + return SetFileAttributesW.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * BOOL SetFileAttributesW(LPCWSTR lpFileName, DWORD dwFileAttributes) + * } + */ + public static MemorySegment SetFileAttributesW$address() { + return SetFileAttributesW.ADDR; + } + + /** + * {@snippet lang=c : + * BOOL SetFileAttributesW(LPCWSTR lpFileName, DWORD dwFileAttributes) + * } + */ + public static int SetFileAttributesW(MemorySegment lpFileName, int dwFileAttributes) { + var mh$ = SetFileAttributesW.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("SetFileAttributesW", lpFileName, dwFileAttributes); + } + return (int)mh$.invokeExact(lpFileName, dwFileAttributes); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + private static final int IO_REPARSE_TAG_SYMLINK = (int)2684354572L; + /** + * {@snippet lang=c : + * #define IO_REPARSE_TAG_SYMLINK 2684354572 + * } + */ + public static int IO_REPARSE_TAG_SYMLINK() { + return IO_REPARSE_TAG_SYMLINK; + } + private static final int INVALID_FILE_ATTRIBUTES = (int)4294967295L; + /** + * {@snippet lang=c : + * #define INVALID_FILE_ATTRIBUTES 4294967295 + * } + */ + public static int INVALID_FILE_ATTRIBUTES() { + return INVALID_FILE_ATTRIBUTES; + } + private static final MemorySegment INVALID_HANDLE_VALUE = MemorySegment.ofAddress(-1L); + /** + * {@snippet lang=c : + * #define INVALID_HANDLE_VALUE (void*) -1 + * } + */ + public static MemorySegment INVALID_HANDLE_VALUE() { + return INVALID_HANDLE_VALUE; + } + private static final int ERROR_FILE_NOT_FOUND = (int)2L; + /** + * {@snippet lang=c : + * #define ERROR_FILE_NOT_FOUND 2 + * } + */ + public static int ERROR_FILE_NOT_FOUND() { + return ERROR_FILE_NOT_FOUND; + } + private static final int ERROR_PATH_NOT_FOUND = (int)3L; + /** + * {@snippet lang=c : + * #define ERROR_PATH_NOT_FOUND 3 + * } + */ + public static int ERROR_PATH_NOT_FOUND() { + return ERROR_PATH_NOT_FOUND; + } +} + diff --git a/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/WIN32_FIND_DATAW.java b/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/WIN32_FIND_DATAW.java new file mode 100644 index 00000000000..511a8e4886d --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/WIN32_FIND_DATAW.java @@ -0,0 +1,37 @@ +// Generated by jextract + +package com.microsoft.windows; + +import java.lang.invoke.*; +import java.lang.foreign.*; +import java.nio.ByteOrder; +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +import static java.lang.foreign.ValueLayout.*; +import static java.lang.foreign.MemoryLayout.PathElement.*; + +/** + * {@snippet lang=c : + * typedef struct _WIN32_FIND_DATAW { + * DWORD dwFileAttributes; + * FILETIME ftCreationTime; + * FILETIME ftLastAccessTime; + * FILETIME ftLastWriteTime; + * DWORD nFileSizeHigh; + * DWORD nFileSizeLow; + * DWORD dwReserved0; + * DWORD dwReserved1; + * WCHAR cFileName[260]; + * WCHAR cAlternateFileName[14]; + * } WIN32_FIND_DATAW + * } + */ +public class WIN32_FIND_DATAW extends _WIN32_FIND_DATAW { + + WIN32_FIND_DATAW() { + // Should not be called directly + } +} + diff --git a/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/_FILETIME.java b/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/_FILETIME.java new file mode 100644 index 00000000000..73aefd18651 --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/_FILETIME.java @@ -0,0 +1,173 @@ +// Generated by jextract + +package com.microsoft.windows; + +import java.lang.invoke.*; +import java.lang.foreign.*; +import java.nio.ByteOrder; +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +import static java.lang.foreign.ValueLayout.*; +import static java.lang.foreign.MemoryLayout.PathElement.*; + +/** + * {@snippet lang=c : + * struct _FILETIME { + * DWORD dwLowDateTime; + * DWORD dwHighDateTime; + * } + * } + */ +public class _FILETIME { + + _FILETIME() { + // Should not be called directly + } + + private static final GroupLayout $LAYOUT = MemoryLayout.structLayout( + FileAPI.C_LONG.withName("dwLowDateTime"), + FileAPI.C_LONG.withName("dwHighDateTime") + ).withName("_FILETIME"); + + /** + * The layout of this struct + */ + public static final GroupLayout layout() { + return $LAYOUT; + } + + private static final OfInt dwLowDateTime$LAYOUT = (OfInt)$LAYOUT.select(groupElement("dwLowDateTime")); + + /** + * Layout for field: + * {@snippet lang=c : + * DWORD dwLowDateTime + * } + */ + public static final OfInt dwLowDateTime$layout() { + return dwLowDateTime$LAYOUT; + } + + private static final long dwLowDateTime$OFFSET = $LAYOUT.byteOffset(groupElement("dwLowDateTime")); + + /** + * Offset for field: + * {@snippet lang=c : + * DWORD dwLowDateTime + * } + */ + public static final long dwLowDateTime$offset() { + return dwLowDateTime$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * DWORD dwLowDateTime + * } + */ + public static int dwLowDateTime(MemorySegment struct) { + return struct.get(dwLowDateTime$LAYOUT, dwLowDateTime$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * DWORD dwLowDateTime + * } + */ + public static void dwLowDateTime(MemorySegment struct, int fieldValue) { + struct.set(dwLowDateTime$LAYOUT, dwLowDateTime$OFFSET, fieldValue); + } + + private static final OfInt dwHighDateTime$LAYOUT = (OfInt)$LAYOUT.select(groupElement("dwHighDateTime")); + + /** + * Layout for field: + * {@snippet lang=c : + * DWORD dwHighDateTime + * } + */ + public static final OfInt dwHighDateTime$layout() { + return dwHighDateTime$LAYOUT; + } + + private static final long dwHighDateTime$OFFSET = $LAYOUT.byteOffset(groupElement("dwHighDateTime")); + + /** + * Offset for field: + * {@snippet lang=c : + * DWORD dwHighDateTime + * } + */ + public static final long dwHighDateTime$offset() { + return dwHighDateTime$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * DWORD dwHighDateTime + * } + */ + public static int dwHighDateTime(MemorySegment struct) { + return struct.get(dwHighDateTime$LAYOUT, dwHighDateTime$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * DWORD dwHighDateTime + * } + */ + public static void dwHighDateTime(MemorySegment struct, int fieldValue) { + struct.set(dwHighDateTime$LAYOUT, dwHighDateTime$OFFSET, fieldValue); + } + + /** + * Obtains a slice of {@code arrayParam} which selects the array element at {@code index}. + * The returned segment has address {@code arrayParam.address() + index * layout().byteSize()} + */ + public static MemorySegment asSlice(MemorySegment array, long index) { + return array.asSlice(layout().byteSize() * index); + } + + /** + * The size (in bytes) of this struct + */ + public static long sizeof() { return layout().byteSize(); } + + /** + * Allocate a segment of size {@code layout().byteSize()} using {@code allocator} + */ + public static MemorySegment allocate(SegmentAllocator allocator) { + return allocator.allocate(layout()); + } + + /** + * Allocate an array of size {@code elementCount} using {@code allocator}. + * The returned segment has size {@code elementCount * layout().byteSize()}. + */ + public static MemorySegment allocateArray(long elementCount, SegmentAllocator allocator) { + return allocator.allocate(MemoryLayout.sequenceLayout(elementCount, layout())); + } + + /** + * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any). + * The returned segment has size {@code layout().byteSize()} + */ + public static MemorySegment reinterpret(MemorySegment addr, Arena arena, Consumer cleanup) { + return reinterpret(addr, 1, arena, cleanup); + } + + /** + * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any). + * The returned segment has size {@code elementCount * layout().byteSize()} + */ + public static MemorySegment reinterpret(MemorySegment addr, long elementCount, Arena arena, Consumer cleanup) { + return addr.reinterpret(layout().byteSize() * elementCount, arena, cleanup); + } +} + diff --git a/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/_WIN32_FIND_DATAW.java b/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/_WIN32_FIND_DATAW.java new file mode 100644 index 00000000000..761dd6abc42 --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src-gen/com/microsoft/windows/_WIN32_FIND_DATAW.java @@ -0,0 +1,607 @@ +// Generated by jextract + +package com.microsoft.windows; + +import java.lang.invoke.*; +import java.lang.foreign.*; +import java.nio.ByteOrder; +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +import static java.lang.foreign.ValueLayout.*; +import static java.lang.foreign.MemoryLayout.PathElement.*; + +/** + * {@snippet lang=c : + * struct _WIN32_FIND_DATAW { + * DWORD dwFileAttributes; + * FILETIME ftCreationTime; + * FILETIME ftLastAccessTime; + * FILETIME ftLastWriteTime; + * DWORD nFileSizeHigh; + * DWORD nFileSizeLow; + * DWORD dwReserved0; + * DWORD dwReserved1; + * WCHAR cFileName[260]; + * WCHAR cAlternateFileName[14]; + * } + * } + */ +public class _WIN32_FIND_DATAW { + + _WIN32_FIND_DATAW() { + // Should not be called directly + } + + private static final GroupLayout $LAYOUT = MemoryLayout.structLayout( + FileAPI.C_LONG.withName("dwFileAttributes"), + _FILETIME.layout().withName("ftCreationTime"), + _FILETIME.layout().withName("ftLastAccessTime"), + _FILETIME.layout().withName("ftLastWriteTime"), + FileAPI.C_LONG.withName("nFileSizeHigh"), + FileAPI.C_LONG.withName("nFileSizeLow"), + FileAPI.C_LONG.withName("dwReserved0"), + FileAPI.C_LONG.withName("dwReserved1"), + MemoryLayout.sequenceLayout(260, FileAPI.C_SHORT).withName("cFileName"), + MemoryLayout.sequenceLayout(14, FileAPI.C_SHORT).withName("cAlternateFileName") + ).withName("_WIN32_FIND_DATAW"); + + /** + * The layout of this struct + */ + public static final GroupLayout layout() { + return $LAYOUT; + } + + private static final OfInt dwFileAttributes$LAYOUT = (OfInt)$LAYOUT.select(groupElement("dwFileAttributes")); + + /** + * Layout for field: + * {@snippet lang=c : + * DWORD dwFileAttributes + * } + */ + public static final OfInt dwFileAttributes$layout() { + return dwFileAttributes$LAYOUT; + } + + private static final long dwFileAttributes$OFFSET = $LAYOUT.byteOffset(groupElement("dwFileAttributes")); + + /** + * Offset for field: + * {@snippet lang=c : + * DWORD dwFileAttributes + * } + */ + public static final long dwFileAttributes$offset() { + return dwFileAttributes$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * DWORD dwFileAttributes + * } + */ + public static int dwFileAttributes(MemorySegment struct) { + return struct.get(dwFileAttributes$LAYOUT, dwFileAttributes$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * DWORD dwFileAttributes + * } + */ + public static void dwFileAttributes(MemorySegment struct, int fieldValue) { + struct.set(dwFileAttributes$LAYOUT, dwFileAttributes$OFFSET, fieldValue); + } + + private static final GroupLayout ftCreationTime$LAYOUT = (GroupLayout)$LAYOUT.select(groupElement("ftCreationTime")); + + /** + * Layout for field: + * {@snippet lang=c : + * FILETIME ftCreationTime + * } + */ + public static final GroupLayout ftCreationTime$layout() { + return ftCreationTime$LAYOUT; + } + + private static final long ftCreationTime$OFFSET = $LAYOUT.byteOffset(groupElement("ftCreationTime")); + + /** + * Offset for field: + * {@snippet lang=c : + * FILETIME ftCreationTime + * } + */ + public static final long ftCreationTime$offset() { + return ftCreationTime$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * FILETIME ftCreationTime + * } + */ + public static MemorySegment ftCreationTime(MemorySegment struct) { + return struct.asSlice(ftCreationTime$OFFSET, ftCreationTime$LAYOUT.byteSize()); + } + + /** + * Setter for field: + * {@snippet lang=c : + * FILETIME ftCreationTime + * } + */ + public static void ftCreationTime(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, ftCreationTime$OFFSET, ftCreationTime$LAYOUT.byteSize()); + } + + private static final GroupLayout ftLastAccessTime$LAYOUT = (GroupLayout)$LAYOUT.select(groupElement("ftLastAccessTime")); + + /** + * Layout for field: + * {@snippet lang=c : + * FILETIME ftLastAccessTime + * } + */ + public static final GroupLayout ftLastAccessTime$layout() { + return ftLastAccessTime$LAYOUT; + } + + private static final long ftLastAccessTime$OFFSET = $LAYOUT.byteOffset(groupElement("ftLastAccessTime")); + + /** + * Offset for field: + * {@snippet lang=c : + * FILETIME ftLastAccessTime + * } + */ + public static final long ftLastAccessTime$offset() { + return ftLastAccessTime$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * FILETIME ftLastAccessTime + * } + */ + public static MemorySegment ftLastAccessTime(MemorySegment struct) { + return struct.asSlice(ftLastAccessTime$OFFSET, ftLastAccessTime$LAYOUT.byteSize()); + } + + /** + * Setter for field: + * {@snippet lang=c : + * FILETIME ftLastAccessTime + * } + */ + public static void ftLastAccessTime(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, ftLastAccessTime$OFFSET, ftLastAccessTime$LAYOUT.byteSize()); + } + + private static final GroupLayout ftLastWriteTime$LAYOUT = (GroupLayout)$LAYOUT.select(groupElement("ftLastWriteTime")); + + /** + * Layout for field: + * {@snippet lang=c : + * FILETIME ftLastWriteTime + * } + */ + public static final GroupLayout ftLastWriteTime$layout() { + return ftLastWriteTime$LAYOUT; + } + + private static final long ftLastWriteTime$OFFSET = $LAYOUT.byteOffset(groupElement("ftLastWriteTime")); + + /** + * Offset for field: + * {@snippet lang=c : + * FILETIME ftLastWriteTime + * } + */ + public static final long ftLastWriteTime$offset() { + return ftLastWriteTime$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * FILETIME ftLastWriteTime + * } + */ + public static MemorySegment ftLastWriteTime(MemorySegment struct) { + return struct.asSlice(ftLastWriteTime$OFFSET, ftLastWriteTime$LAYOUT.byteSize()); + } + + /** + * Setter for field: + * {@snippet lang=c : + * FILETIME ftLastWriteTime + * } + */ + public static void ftLastWriteTime(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, ftLastWriteTime$OFFSET, ftLastWriteTime$LAYOUT.byteSize()); + } + + private static final OfInt nFileSizeHigh$LAYOUT = (OfInt)$LAYOUT.select(groupElement("nFileSizeHigh")); + + /** + * Layout for field: + * {@snippet lang=c : + * DWORD nFileSizeHigh + * } + */ + public static final OfInt nFileSizeHigh$layout() { + return nFileSizeHigh$LAYOUT; + } + + private static final long nFileSizeHigh$OFFSET = $LAYOUT.byteOffset(groupElement("nFileSizeHigh")); + + /** + * Offset for field: + * {@snippet lang=c : + * DWORD nFileSizeHigh + * } + */ + public static final long nFileSizeHigh$offset() { + return nFileSizeHigh$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * DWORD nFileSizeHigh + * } + */ + public static int nFileSizeHigh(MemorySegment struct) { + return struct.get(nFileSizeHigh$LAYOUT, nFileSizeHigh$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * DWORD nFileSizeHigh + * } + */ + public static void nFileSizeHigh(MemorySegment struct, int fieldValue) { + struct.set(nFileSizeHigh$LAYOUT, nFileSizeHigh$OFFSET, fieldValue); + } + + private static final OfInt nFileSizeLow$LAYOUT = (OfInt)$LAYOUT.select(groupElement("nFileSizeLow")); + + /** + * Layout for field: + * {@snippet lang=c : + * DWORD nFileSizeLow + * } + */ + public static final OfInt nFileSizeLow$layout() { + return nFileSizeLow$LAYOUT; + } + + private static final long nFileSizeLow$OFFSET = $LAYOUT.byteOffset(groupElement("nFileSizeLow")); + + /** + * Offset for field: + * {@snippet lang=c : + * DWORD nFileSizeLow + * } + */ + public static final long nFileSizeLow$offset() { + return nFileSizeLow$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * DWORD nFileSizeLow + * } + */ + public static int nFileSizeLow(MemorySegment struct) { + return struct.get(nFileSizeLow$LAYOUT, nFileSizeLow$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * DWORD nFileSizeLow + * } + */ + public static void nFileSizeLow(MemorySegment struct, int fieldValue) { + struct.set(nFileSizeLow$LAYOUT, nFileSizeLow$OFFSET, fieldValue); + } + + private static final OfInt dwReserved0$LAYOUT = (OfInt)$LAYOUT.select(groupElement("dwReserved0")); + + /** + * Layout for field: + * {@snippet lang=c : + * DWORD dwReserved0 + * } + */ + public static final OfInt dwReserved0$layout() { + return dwReserved0$LAYOUT; + } + + private static final long dwReserved0$OFFSET = $LAYOUT.byteOffset(groupElement("dwReserved0")); + + /** + * Offset for field: + * {@snippet lang=c : + * DWORD dwReserved0 + * } + */ + public static final long dwReserved0$offset() { + return dwReserved0$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * DWORD dwReserved0 + * } + */ + public static int dwReserved0(MemorySegment struct) { + return struct.get(dwReserved0$LAYOUT, dwReserved0$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * DWORD dwReserved0 + * } + */ + public static void dwReserved0(MemorySegment struct, int fieldValue) { + struct.set(dwReserved0$LAYOUT, dwReserved0$OFFSET, fieldValue); + } + + private static final OfInt dwReserved1$LAYOUT = (OfInt)$LAYOUT.select(groupElement("dwReserved1")); + + /** + * Layout for field: + * {@snippet lang=c : + * DWORD dwReserved1 + * } + */ + public static final OfInt dwReserved1$layout() { + return dwReserved1$LAYOUT; + } + + private static final long dwReserved1$OFFSET = $LAYOUT.byteOffset(groupElement("dwReserved1")); + + /** + * Offset for field: + * {@snippet lang=c : + * DWORD dwReserved1 + * } + */ + public static final long dwReserved1$offset() { + return dwReserved1$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * DWORD dwReserved1 + * } + */ + public static int dwReserved1(MemorySegment struct) { + return struct.get(dwReserved1$LAYOUT, dwReserved1$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * DWORD dwReserved1 + * } + */ + public static void dwReserved1(MemorySegment struct, int fieldValue) { + struct.set(dwReserved1$LAYOUT, dwReserved1$OFFSET, fieldValue); + } + + private static final SequenceLayout cFileName$LAYOUT = (SequenceLayout)$LAYOUT.select(groupElement("cFileName")); + + /** + * Layout for field: + * {@snippet lang=c : + * WCHAR cFileName[260] + * } + */ + public static final SequenceLayout cFileName$layout() { + return cFileName$LAYOUT; + } + + private static final long cFileName$OFFSET = $LAYOUT.byteOffset(groupElement("cFileName")); + + /** + * Offset for field: + * {@snippet lang=c : + * WCHAR cFileName[260] + * } + */ + public static final long cFileName$offset() { + return cFileName$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * WCHAR cFileName[260] + * } + */ + public static MemorySegment cFileName(MemorySegment struct) { + return struct.asSlice(cFileName$OFFSET, cFileName$LAYOUT.byteSize()); + } + + /** + * Setter for field: + * {@snippet lang=c : + * WCHAR cFileName[260] + * } + */ + public static void cFileName(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, cFileName$OFFSET, cFileName$LAYOUT.byteSize()); + } + + private static long[] cFileName$DIMS = { 260 }; + + /** + * Dimensions for array field: + * {@snippet lang=c : + * WCHAR cFileName[260] + * } + */ + public static long[] cFileName$dimensions() { + return cFileName$DIMS; + } + private static final VarHandle cFileName$ELEM_HANDLE = cFileName$LAYOUT.varHandle(sequenceElement()); + + /** + * Indexed getter for field: + * {@snippet lang=c : + * WCHAR cFileName[260] + * } + */ + public static short cFileName(MemorySegment struct, long index0) { + return (short)cFileName$ELEM_HANDLE.get(struct, cFileName$OFFSET, index0); + } + + /** + * Indexed setter for field: + * {@snippet lang=c : + * WCHAR cFileName[260] + * } + */ + public static void cFileName(MemorySegment struct, long index0, short fieldValue) { + cFileName$ELEM_HANDLE.set(struct, cFileName$OFFSET, index0, fieldValue); + } + + private static final SequenceLayout cAlternateFileName$LAYOUT = (SequenceLayout)$LAYOUT.select(groupElement("cAlternateFileName")); + + /** + * Layout for field: + * {@snippet lang=c : + * WCHAR cAlternateFileName[14] + * } + */ + public static final SequenceLayout cAlternateFileName$layout() { + return cAlternateFileName$LAYOUT; + } + + private static final long cAlternateFileName$OFFSET = $LAYOUT.byteOffset(groupElement("cAlternateFileName")); + + /** + * Offset for field: + * {@snippet lang=c : + * WCHAR cAlternateFileName[14] + * } + */ + public static final long cAlternateFileName$offset() { + return cAlternateFileName$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * WCHAR cAlternateFileName[14] + * } + */ + public static MemorySegment cAlternateFileName(MemorySegment struct) { + return struct.asSlice(cAlternateFileName$OFFSET, cAlternateFileName$LAYOUT.byteSize()); + } + + /** + * Setter for field: + * {@snippet lang=c : + * WCHAR cAlternateFileName[14] + * } + */ + public static void cAlternateFileName(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, cAlternateFileName$OFFSET, cAlternateFileName$LAYOUT.byteSize()); + } + + private static long[] cAlternateFileName$DIMS = { 14 }; + + /** + * Dimensions for array field: + * {@snippet lang=c : + * WCHAR cAlternateFileName[14] + * } + */ + public static long[] cAlternateFileName$dimensions() { + return cAlternateFileName$DIMS; + } + private static final VarHandle cAlternateFileName$ELEM_HANDLE = cAlternateFileName$LAYOUT.varHandle(sequenceElement()); + + /** + * Indexed getter for field: + * {@snippet lang=c : + * WCHAR cAlternateFileName[14] + * } + */ + public static short cAlternateFileName(MemorySegment struct, long index0) { + return (short)cAlternateFileName$ELEM_HANDLE.get(struct, cAlternateFileName$OFFSET, index0); + } + + /** + * Indexed setter for field: + * {@snippet lang=c : + * WCHAR cAlternateFileName[14] + * } + */ + public static void cAlternateFileName(MemorySegment struct, long index0, short fieldValue) { + cAlternateFileName$ELEM_HANDLE.set(struct, cAlternateFileName$OFFSET, index0, fieldValue); + } + + /** + * Obtains a slice of {@code arrayParam} which selects the array element at {@code index}. + * The returned segment has address {@code arrayParam.address() + index * layout().byteSize()} + */ + public static MemorySegment asSlice(MemorySegment array, long index) { + return array.asSlice(layout().byteSize() * index); + } + + /** + * The size (in bytes) of this struct + */ + public static long sizeof() { return layout().byteSize(); } + + /** + * Allocate a segment of size {@code layout().byteSize()} using {@code allocator} + */ + public static MemorySegment allocate(SegmentAllocator allocator) { + return allocator.allocate(layout()); + } + + /** + * Allocate an array of size {@code elementCount} using {@code allocator}. + * The returned segment has size {@code elementCount * layout().byteSize()}. + */ + public static MemorySegment allocateArray(long elementCount, SegmentAllocator allocator) { + return allocator.allocate(MemoryLayout.sequenceLayout(elementCount, layout())); + } + + /** + * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any). + * The returned segment has size {@code layout().byteSize()} + */ + public static MemorySegment reinterpret(MemorySegment addr, Arena arena, Consumer cleanup) { + return reinterpret(addr, 1, arena, cleanup); + } + + /** + * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction} (if any). + * The returned segment has size {@code elementCount * layout().byteSize()} + */ + public static MemorySegment reinterpret(MemorySegment addr, long elementCount, Arena arena, Consumer cleanup) { + return addr.reinterpret(layout().byteSize() * elementCount, arena, cleanup); + } +} + diff --git a/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/Win32Handler.java b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/Win32Handler.java index 30405005f3f..ff02136b0b1 100644 --- a/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/Win32Handler.java +++ b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/Win32Handler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024, 2024 Hannes Wellmann and others. + * Copyright (c) 2024, 2026 Hannes Wellmann and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -10,32 +10,42 @@ * * Contributors: * Hannes Wellmann - initial API and implementation + * Hannes Wellmann - Migrate from JNA to FFM API *******************************************************************************/ package org.eclipse.core.internal.filesystem.local; import static org.eclipse.core.internal.filesystem.local.Convert.WIN32_RAW_PATH_PREFIX; import static org.eclipse.core.internal.filesystem.local.Convert.WIN32_UNC_RAW_PATH_PREFIX; -import com.sun.jna.Memory; -import com.sun.jna.Native; -import com.sun.jna.NativeLibrary; -import com.sun.jna.Pointer; -import com.sun.jna.WString; -import com.sun.jna.platform.win32.WinBase; -import com.sun.jna.platform.win32.WinDef; -import com.sun.jna.platform.win32.WinError; -import com.sun.jna.platform.win32.WinNT; +import com.microsoft.windows.FILETIME; +import com.microsoft.windows.FileAPI; +import com.microsoft.windows.WIN32_FIND_DATAW; +import java.io.File; import java.io.IOException; +import java.lang.foreign.Arena; +import java.lang.foreign.Linker; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.StructLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.VarHandle; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Date; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.Month; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileInfo; import org.eclipse.core.filesystem.provider.FileInfo; /** * A NativeHandler for Windows file systems that supports legacy {@code DOS} attributes and - * uses the Windows {@code fileapi.h} API called through JNA. + * calls the Windows {@code fileapi.h} API directly through the Java foreigen functions API. + * + * See the implementation notes for the rational for direct native method invocations. */ public class Win32Handler extends NativeHandler { private static final int ATTRIBUTES = EFS.ATTRIBUTE_SYMLINK | EFS.ATTRIBUTE_LINK_TARGET // symbolic link support @@ -46,6 +56,18 @@ public int getSupportedAttributes() { return ATTRIBUTES; } + /** + * Fetches the {@link IFileInfo} of the given file. + * + * @implNote + * This implementation invokes the native {@code FindFirstFileW} method of the Windows API directly + * to obtain the {@code WIN32_FIND_DATA} for only the last segment of the given file-path. + * The Windows file-system is case-insensitive and we need to determine the real casing of the filename, therefore calling this search method is necessary. + * The only available Java APIs for this are {@link Path#toRealPath(java.nio.file.LinkOption...)} and {@link File#getCanonicalPath()}. + * Internally these methods also call the native {@code FindFirstFileW} method, but for each segment of the path. + * Because only the last segment is relevant and considered here, searching the real name of each parent is unnecessary and wasteful. + * Depending on the length of the path, this implementation is consequently multiple times, up to a magnitude faster than the mentioned Java API. + */ @Override public FileInfo fetchFileInfo(String fileName) { FileInfo fileInfo = new FileInfo(); @@ -59,24 +81,26 @@ public FileInfo fetchFileInfo(String fileName) { fileInfo.setExists(Files.exists(Path.of(target.substring(WIN32_RAW_PATH_PREFIX.length())))); return fileInfo; } - - try (Memory mem = new Memory(WIN32_FIND_DATA_SIZE)) { - // Allocating (uninitialized) memory explicitly in advance is faster than using - // the slightly more convenient direct instantiation of a JNA WinBase.WIN32_FIND_DATA structure, - // probably because the latter perform also an initial autowrite and computes the size each time. - // For the same reason it is again faster to read the data-structure 'manually'. - long handle = FileAPIh.FindFirstFileW(new WString(target), mem); - if (handle == FileAPIh.INVALID_HANDLE_VALUE) { - int error = Native.getLastError(); - if (!(error == WinError.ERROR_FILE_NOT_FOUND // file not found in existing parent directory - || error == WinError.ERROR_PATH_NOT_FOUND)) { // Not even the parent directory exists + try (Arena arena = Arena.ofConfined()) { + MemorySegment lpFileName = allocateWideString(target, arena); + @SuppressWarnings("static-access") + MemorySegment lpFindFileData = arena.allocate(WIN32_FIND_DATAW.layout()); + MemorySegment capturedError = arena.allocate(LAST_ERROR_CAPTURE_LAYOUT); + + // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfilew + MemorySegment handle = FindFirstFileW(lpFileName, lpFindFileData, capturedError); + + if (FileAPI.INVALID_HANDLE_VALUE().equals(handle)) { + int error = GetLastError(capturedError); + if (!(error == FileAPI.ERROR_FILE_NOT_FOUND() // file not found in existing parent directory + || error == FileAPI.ERROR_PATH_NOT_FOUND())) { // Not even the parent directory exists fileInfo.setError(IFileInfo.IO_ERROR); } return fileInfo; } - FileAPIh.FindClose(handle); + FileAPI.FindClose(handle); - convertFindDataWToFileInfo(mem, fileInfo, fileName); + convertFindDataWToFileInfo(lpFindFileData, fileInfo, fileName); } catch (IOException e) { // Leave alone and continue. The name is set before an IOException can be thrown fileInfo.setError(IFileInfo.IO_ERROR); @@ -84,43 +108,93 @@ public FileInfo fetchFileInfo(String fileName) { return fileInfo; } + private static final StructLayout LAST_ERROR_CAPTURE_LAYOUT = Linker.Option.captureStateLayout(); + private static final VarHandle GET_LAST_ERROR_HANDLE = LAST_ERROR_CAPTURE_LAYOUT.varHandle(// + MemoryLayout.PathElement.groupElement("GetLastError")); //$NON-NLS-1$ + private static final MethodHandle FIND_FIRST_FILE__W_HANDLE = Linker.nativeLinker().downcallHandle( // + FileAPI.FindFirstFileW$address(), FileAPI.FindFirstFileW$descriptor(), // + Linker.Option.captureCallState("GetLastError")); //$NON-NLS-1$ + + /** + * Calls the native method {@code FindFirstFileW}, ensuring the {@code GetLastError()} method usable. + *

+ * Enable "the linker option used to save portions of the execution state immediately after calling a foreign function associated with a downcall method handle, + * before it can be overwritten by the Java runtime". + * For more details, see https://docs.oracle.com/en/java/javase/25/core/checking-native-errors-using-errno.html + *

+ * @see java.lang.foreign.Linker.Option#captureCallState(String...) + */ + private static MemorySegment FindFirstFileW(MemorySegment lpFileName, MemorySegment lpFindFileData, MemorySegment capturedError) { + try { + return (MemorySegment) FIND_FIRST_FILE__W_HANDLE.invokeExact(capturedError, lpFileName, lpFindFileData); + } catch (Error | RuntimeException e) { + throw e; + } catch (Throwable e) { + throw new AssertionError("should not reach here", e); //$NON-NLS-1$ + } + } + + private static int GetLastError(MemorySegment capturedError) { + return (int) GET_LAST_ERROR_HANDLE.get(capturedError, 0L); + } + + /** + * Sets the given {@link IFileInfo} to the given file. + * + * @implNote + * This implementation invokes the native Windows API methods {@code GetFileAttributesW} and {@code SetFileAttributesW} to read and write the file attributes. + * It allows to set the updated file attributes only once, after all modifications are applied and only if there is any overall change. + * Through {@link java.nio.file.attribute.DosFileAttributeView} each file attribute can only be set individually, + * which leads to one native file-attributes get and (up to) one set invocation per attribute. + * The NIO API does not provide means for batch updates. + * Since there are currently there file attributes considered, using the Java NIO API would consequently be up to three times slower. + */ @Override public boolean putFileInfo(String fileName, IFileInfo info, int options) { - WString lpFileName = new WString(toLongWindowsPath(fileName)); - long dwFileAttributes = FileAPIh.GetFileAttributesW(lpFileName); - if (dwFileAttributes == FileAPIh.INVALID_FILE_ATTRIBUTES) { - return false; - } - if (dwFileAttributes == WinNT.FILE_ATTRIBUTE_NORMAL) { - // Assume nothing is set, as the documentation of FILE_ATTRIBUTE_NORMAL states: - // "A file that does not have other attributes set. This attribute is valid only when used alone." - dwFileAttributes = 0; - } - long fileAttributes = dwFileAttributes; + String longFilename = toLongWindowsPath(fileName); + try (Arena arena = Arena.ofConfined()) { + MemorySegment lpFileName = allocateWideString(longFilename, arena); + // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileattributesw + int dwFileAttributes = FileAPI.GetFileAttributesW(lpFileName); + if (dwFileAttributes == FileAPI.INVALID_FILE_ATTRIBUTES()) { + return false; + } + if (dwFileAttributes == FileAPI.FILE_ATTRIBUTE_NORMAL()) { + // Assume nothing is set, as the documentation of FILE_ATTRIBUTE_NORMAL states: + // "A file that does not have other attributes set. This attribute is valid only when used alone." + dwFileAttributes = 0; + } + int fileAttributes = dwFileAttributes; - boolean archive = info.getAttribute(EFS.ATTRIBUTE_ARCHIVE); - boolean readOnly = info.getAttribute(EFS.ATTRIBUTE_READ_ONLY); - boolean hidden = info.getAttribute(EFS.ATTRIBUTE_HIDDEN); - fileAttributes = set(fileAttributes, WinNT.FILE_ATTRIBUTE_ARCHIVE, archive); - fileAttributes = set(fileAttributes, WinNT.FILE_ATTRIBUTE_READONLY, readOnly); - fileAttributes = set(fileAttributes, WinNT.FILE_ATTRIBUTE_HIDDEN, hidden); + boolean archive = info.getAttribute(EFS.ATTRIBUTE_ARCHIVE); + boolean readOnly = info.getAttribute(EFS.ATTRIBUTE_READ_ONLY); + boolean hidden = info.getAttribute(EFS.ATTRIBUTE_HIDDEN); + fileAttributes = set(fileAttributes, FileAPI.FILE_ATTRIBUTE_ARCHIVE(), archive); + fileAttributes = set(fileAttributes, FileAPI.FILE_ATTRIBUTE_READONLY(), readOnly); + fileAttributes = set(fileAttributes, FileAPI.FILE_ATTRIBUTE_HIDDEN(), hidden); - if (dwFileAttributes == fileAttributes) { - return true; // Everything is already up to date -> nothing to do + if (dwFileAttributes == fileAttributes) { + return true; // Everything is already up to date -> nothing to do + } + // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesw + return FileAPI.SetFileAttributesW(lpFileName, fileAttributes) != 0; } - return FileAPIh.SetFileAttributesW(lpFileName, fileAttributes); } public static String getShortPathName(String longPath) { longPath = toLongWindowsPath(longPath); - char[] buffer = new char[longPath.length()]; // https://learn.microsoft.com/de-de/windows/win32/api/fileapi/nf-fileapi-getshortpathnamew - int newLength = com.sun.jna.platform.win32.Kernel32.INSTANCE.GetShortPathName(longPath, buffer, buffer.length); - if (0 < newLength && newLength < buffer.length) { // zero means error - int offset = longPath.startsWith(WIN32_UNC_RAW_PATH_PREFIX) ? WIN32_UNC_RAW_PATH_PREFIX.length() : WIN32_RAW_PATH_PREFIX.length(); - return new String(buffer, offset, newLength - offset); + try (Arena arena = Arena.ofConfined()) { + MemorySegment lpszLongPath = allocateWideString(longPath, arena); + MemorySegment lpszShortPath = arena.allocate(lpszLongPath.byteSize()); // short name should be shorter -> will fit into array + + int newLength = FileAPI.GetShortPathNameW(lpszLongPath, lpszShortPath, longPath.length()); + if (0 < newLength && newLength < longPath.length()) { // zero means error and if not shorter it's not useful + int offset = longPath.startsWith(WIN32_UNC_RAW_PATH_PREFIX) ? WIN32_UNC_RAW_PATH_PREFIX.length() : WIN32_RAW_PATH_PREFIX.length(); + return getWideString(lpszShortPath).substring(offset); + } + return null; } - return null; } private static String toLongWindowsPath(String fileName) { @@ -135,122 +209,70 @@ private static String toLongWindowsPath(String fileName) { return fileName; } - static class FileAPIh { - static { - Native.register(NativeLibrary.getInstance("Kernel32" /* , W32APIOptions.DEFAULT_OPTIONS */ )); //$NON-NLS-1$ - // Not using W32APIOptions.DEFAULT_OPTIONS requires the usage of a few special types (e.g. WString) but improves performance. - } - private static final long INVALID_HANDLE_VALUE = Pointer.nativeValue(WinBase.INVALID_HANDLE_VALUE.getPointer()); - - // winnt.h HANDLE can be expressed as java long - - // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfilew - static native long FindFirstFileW(WString lpFileName, Pointer lpFindFileData); - - // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileattributesw - static native long GetFileAttributesW(WString lpFileName); - - // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesw - static native boolean SetFileAttributesW(WString lpFileName, long dwFileAttributes); - - static final long INVALID_FILE_ATTRIBUTES = new WinBase.DWORD(-1).longValue(); - - static native boolean FindClose(long handle); - } - - private static final int DWORD_SIZE = WinBase.DWORD.SIZE; - private static final int FILETIME_SIZE = 2 * DWORD_SIZE; - private static final int WCHAR_SIZE = 2; - - /** - * Read the of the native memory data-strcuture - * {@code WIN32_FIND_DATAW}. - *
-	 *	typedef struct _WIN32_FIND_DATAW {
-	 *		DWORD dwFileAttributes;
-	 *		FILETIME ftCreationTime;
-	 *		FILETIME ftLastAccessTime;
-	 *		FILETIME ftLastWriteTime;
-	 *		DWORD nFileSizeHigh;
-	 *		DWORD nFileSizeLow;
-	 *		DWORD dwReserved0;
-	 *		DWORD dwReserved1;
-	 *		_Field_z_ WCHAR  cFileName[ MAX_PATH ];
-	 *		_Field_z_ WCHAR  cAlternateFileName[ 14 ];
-	 *	} WIN32_FIND_DATAW
-	 * 
- */ - private static final int DW_FILE_ATTRIBUTES = 0; - private static final int FT_CREATION_TIME = DW_FILE_ATTRIBUTES + DWORD_SIZE; - private static final int FT_LAST_ACCESS_TIME = FT_CREATION_TIME + FILETIME_SIZE; - private static final int FT_LAST_WRITE_TIME = FT_LAST_ACCESS_TIME + FILETIME_SIZE; - private static final int N_FILE_SIZE_HIGH = FT_LAST_WRITE_TIME + FILETIME_SIZE; - private static final int N_FILE_SIZE_LOW = N_FILE_SIZE_HIGH + DWORD_SIZE; - private static final int DW_RESERVED_0 = N_FILE_SIZE_LOW + DWORD_SIZE; - private static final int DW_RESERVED_1 = DW_RESERVED_0 + DWORD_SIZE; - private static final int C_FILE_NAME = DW_RESERVED_1 + DWORD_SIZE; - private static final int C_ALTERNATE_FILE_NAME = C_FILE_NAME + WinDef.MAX_PATH * WCHAR_SIZE; - - private static final int WIN32_FIND_DATA_SIZE = C_ALTERNATE_FILE_NAME + 14 * WCHAR_SIZE; - static { - if (WIN32_FIND_DATA_SIZE != WinBase.WIN32_FIND_DATA.sizeOf()) { - throw new IllegalStateException("Struct 'WIN32_FIND_DATAW' has unexpected size"); //$NON-NLS-1$ - } - } - private static final long MAXDWORD = 0xFFFFFFFFL; // unsigned long from winnt.h. On Windows a C long usually has only 32bit - - private static void convertFindDataWToFileInfo(Memory mem, FileInfo info, String fileName) throws IOException { + @SuppressWarnings("static-access") + private static void convertFindDataWToFileInfo(MemorySegment mem, FileInfo info, String fileName) throws IOException { /** * For possible values of dwFileAttributes and their descriptions, * see File Attribute Constants. */ - int dwFileAttributes = readDWORDAsSignedInt(mem, DW_FILE_ATTRIBUTES); - Date ftLastWriteTime = readFILETIME(mem, FT_LAST_WRITE_TIME); - long nFileSizeHigh = readDWORD(mem, N_FILE_SIZE_HIGH); - long nFileSizeLow = readDWORD(mem, N_FILE_SIZE_LOW); - int dwReserved0 = readDWORDAsSignedInt(mem, DW_RESERVED_0); - String cFileName = mem.getWideString(C_FILE_NAME); + int dwFileAttributes = WIN32_FIND_DATAW.dwFileAttributes(mem); + Instant ftLastWriteTime = readFILETIME(WIN32_FIND_DATAW.ftLastWriteTime(mem)); - long fileLength = (nFileSizeHigh * (MAXDWORD + 1)) + nFileSizeLow; + int nFileSizeHigh = WIN32_FIND_DATAW.nFileSizeHigh(mem); + int nFileSizeLow = WIN32_FIND_DATAW.nFileSizeLow(mem); + int dwReserved0 = WIN32_FIND_DATAW.dwReserved0(mem); + String cFileName = getWideString(WIN32_FIND_DATAW.cFileName(mem)); + + long fileLength = toLong(nFileSizeHigh, nFileSizeLow); info.setName(cFileName); info.setExists(true); - info.setLastModified(ftLastWriteTime.getTime()); + info.setLastModified(ftLastWriteTime.toEpochMilli()); info.setLength(fileLength); - info.setDirectory(isSet(dwFileAttributes, WinNT.FILE_ATTRIBUTE_DIRECTORY)); - info.setAttribute(EFS.ATTRIBUTE_ARCHIVE, isSet(dwFileAttributes, WinNT.FILE_ATTRIBUTE_ARCHIVE)); - info.setAttribute(EFS.ATTRIBUTE_READ_ONLY, isSet(dwFileAttributes, WinNT.FILE_ATTRIBUTE_READONLY)); - info.setAttribute(EFS.ATTRIBUTE_HIDDEN, isSet(dwFileAttributes, WinNT.FILE_ATTRIBUTE_HIDDEN)); + info.setDirectory(isSet(dwFileAttributes, FileAPI.FILE_ATTRIBUTE_DIRECTORY())); + info.setAttribute(EFS.ATTRIBUTE_ARCHIVE, isSet(dwFileAttributes, FileAPI.FILE_ATTRIBUTE_ARCHIVE())); + info.setAttribute(EFS.ATTRIBUTE_READ_ONLY, isSet(dwFileAttributes, FileAPI.FILE_ATTRIBUTE_READONLY())); + info.setAttribute(EFS.ATTRIBUTE_HIDDEN, isSet(dwFileAttributes, FileAPI.FILE_ATTRIBUTE_HIDDEN())); - boolean isReparsePoint = isSet(dwFileAttributes, WinNT.FILE_ATTRIBUTE_REPARSE_POINT); - if (isReparsePoint && dwReserved0 == WinNT.IO_REPARSE_TAG_SYMLINK) { + boolean isReparsePoint = isSet(dwFileAttributes, FileAPI.FILE_ATTRIBUTE_REPARSE_POINT()); + if (isReparsePoint && dwReserved0 == FileAPI.IO_REPARSE_TAG_SYMLINK()) { Path linkTarget = Files.readSymbolicLink(Path.of(fileName)); info.setAttribute(EFS.ATTRIBUTE_SYMLINK, true); info.setStringAttribute(EFS.ATTRIBUTE_LINK_TARGET, linkTarget.toString()); } } - private static int readDWORDAsSignedInt(Memory memory, int offset) { - return memory.getInt(offset); // int is signed + private static final Instant WINDOWS_REFERENCE_DATE = LocalDateTime.of(1601, Month.JANUARY, 1, 0, 0).toInstant(ZoneOffset.UTC); + + // https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime + @SuppressWarnings("static-access") + private static Instant readFILETIME(MemorySegment struct) { + int low = FILETIME.dwLowDateTime(struct); + int high = FILETIME.dwHighDateTime(struct); + final long filetime = toLong(high, low); + return WINDOWS_REFERENCE_DATE.plus(filetime / 10, ChronoUnit.MICROS); } - private static long readDWORD(Memory memory, int offset) { - // From https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types - // "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;" - return readDWORDAsSignedInt(memory, offset) & MAXDWORD; + // https://learn.microsoft.com/en-us/windows/win32/learnwin32/working-with-strings + private static MemorySegment allocateWideString(String longPath, Arena arena) { + return arena.allocateFrom(longPath, StandardCharsets.UTF_16LE); } - private static Date readFILETIME(Memory memory, int offset) { - int low = readDWORDAsSignedInt(memory, offset); - int high = readDWORDAsSignedInt(memory, offset + DWORD_SIZE); - return WinBase.FILETIME.filetimeToDate(high, low); + private static String getWideString(MemorySegment memory) { + return memory.getString(0, StandardCharsets.UTF_16LE); + } + + // See also https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types + + private static long toLong(int highDWORD, int lowDWORD) { + return (long) highDWORD << 32 | lowDWORD & 0xffffffffL; } - private static boolean isSet(long field, int bit) { + private static boolean isSet(int field, int bit) { return (field & bit) != 0; } - private long set(long field, int bit, boolean isSet) { + private int set(int field, int bit, boolean isSet) { return isSet ? (field | bit) : (field & ~bit); }