From c9c2df0d0fd2538ff1eead8115be2ba9e04ff9d2 Mon Sep 17 00:00:00 2001 From: Lars Vogel Date: Sat, 12 Sep 2026 14:38:03 +0200 Subject: [PATCH] Read Linux file attributes through the FFM API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bulk directory listing needs libfastlinuxfile, which only the x86_64 fragment ships, so the other Linux architectures fall back to one stat per child through libunixfile. Bind opendir, readdir, statx, readlinkat and chmod through the Foreign Function & Memory API instead: statx has a kernel defined layout that is identical on every architecture, so one implementation covers all of them without a compiled artifact. The bindings are generated by jextract using the script added with this change, as on Windows. Only statx is bound by hand, from the generated descriptor and symbol, because it needs captureCallState to read errno, which is what tells a file that is not there from one that cannot be read. The buffers are held per thread and reused, so a listing allocates nothing per entry beyond the resulting FileInfo. Listing 270027 entries takes 439 to 464 ms against 537 to 563 ms, with identical results for every entry. One behaviour differs on purpose: a symbolic link whose target cannot be stated keeps its symlink attribute and link target in a listing. The JNI listing dropped both, while its single file path already reported them, and that attribute is what the recursive link detection of UnifiedTree reads. Set -Declipse.filesystem.useFfm=false to fall back to the native libraries. Assisted-by: multiple AI agents and layers of automated tooling 🤖 --- .../generateLinuxH.sh | 50 + .../src-gen/org/linux/LibC$shared.java | 63 + .../src-gen/org/linux/LibC.java | 644 +++++++ .../src-gen/org/linux/dirent.java | 345 ++++ .../src-gen/org/linux/statx.java | 1606 +++++++++++++++++ .../src-gen/org/linux/statx_timestamp.java | 219 +++ .../local/LocalFileNativesManager.java | 30 +- .../filesystem/local/ffm/LinuxFfmHandler.java | 339 ++++ .../filesystem/local/ffm/LinuxLibC.java | 88 + .../tests/resources/perf/BenchFileStore.java | 29 + 10 files changed, 3410 insertions(+), 3 deletions(-) create mode 100755 resources/bundles/org.eclipse.core.filesystem/generateLinuxH.sh create mode 100644 resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/LibC$shared.java create mode 100644 resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/LibC.java create mode 100644 resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/dirent.java create mode 100644 resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/statx.java create mode 100644 resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/statx_timestamp.java create mode 100644 resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/ffm/LinuxFfmHandler.java create mode 100644 resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/ffm/LinuxLibC.java diff --git a/resources/bundles/org.eclipse.core.filesystem/generateLinuxH.sh b/resources/bundles/org.eclipse.core.filesystem/generateLinuxH.sh new file mode 100755 index 00000000000..6898693ad85 --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/generateLinuxH.sh @@ -0,0 +1,50 @@ +#!/bin/sh +# Regenerates the glibc bindings in src-gen. Needs jextract on the PATH, +# https://jdk.java.net/jextract/, and has to run on Linux. +# +# No --library: that would emit libraryLookup("libc.so"), which cannot be opened +# on glibc, where the library is libc.so.6. Without it the generated lookup falls +# back to the linker's default lookup, which already sees libc. +set -eu +cd "$(dirname "$0")" + +cat > /tmp/LibC.h <<'HEADER' +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +HEADER + +jextract --output src-gen \ + --include-function statx \ + --include-function opendir \ + --include-function readdir \ + --include-function closedir \ + --include-function dirfd \ + --include-function readlinkat \ + --include-function chmod \ + --include-struct statx \ + --include-struct statx_timestamp \ + --include-struct dirent \ + --include-constant AT_FDCWD \ + --include-constant AT_SYMLINK_NOFOLLOW \ + --include-constant AT_STATX_SYNC_AS_STAT \ + --include-constant STATX_TYPE \ + --include-constant STATX_MODE \ + --include-constant STATX_SIZE \ + --include-constant STATX_MTIME \ + --include-constant ENOENT \ + --include-constant PATH_MAX \ + --include-constant S_IFMT \ + --include-constant S_IFDIR \ + --include-constant S_IFLNK \ + --include-constant S_IRUSR --include-constant S_IWUSR --include-constant S_IXUSR \ + --include-constant S_IRGRP --include-constant S_IWGRP --include-constant S_IXGRP \ + --include-constant S_IROTH --include-constant S_IWOTH --include-constant S_IXOTH \ + --target-package org.linux \ + --header-class-name LibC \ + /tmp/LibC.h diff --git a/resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/LibC$shared.java b/resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/LibC$shared.java new file mode 100644 index 00000000000..767d35b8a45 --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/LibC$shared.java @@ -0,0 +1,63 @@ +// Generated by jextract + +package org.linux; + +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 LibC$shared { + + LibC$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.OfLong C_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long"); + + 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/org/linux/LibC.java b/resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/LibC.java new file mode 100644 index 00000000000..65dfb585cfa --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/LibC.java @@ -0,0 +1,644 @@ +// Generated by jextract + +package org.linux; + +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 LibC extends LibC$shared { + + LibC() { + // Should not be called directly + } + + static final Arena LIBRARY_ARENA = Arena.ofAuto(); + + static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup() + .or(Linker.nativeLinker().defaultLookup()); + + private static final int PATH_MAX = (int)4096L; + /** + * {@snippet lang=c : + * #define PATH_MAX 4096 + * } + */ + public static int PATH_MAX() { + return PATH_MAX; + } + private static final int ENOENT = (int)2L; + /** + * {@snippet lang=c : + * #define ENOENT 2 + * } + */ + public static int ENOENT() { + return ENOENT; + } + private static final int AT_SYMLINK_NOFOLLOW = (int)256L; + /** + * {@snippet lang=c : + * #define AT_SYMLINK_NOFOLLOW 256 + * } + */ + public static int AT_SYMLINK_NOFOLLOW() { + return AT_SYMLINK_NOFOLLOW; + } + private static final int AT_STATX_SYNC_AS_STAT = (int)0L; + /** + * {@snippet lang=c : + * #define AT_STATX_SYNC_AS_STAT 0 + * } + */ + public static int AT_STATX_SYNC_AS_STAT() { + return AT_STATX_SYNC_AS_STAT; + } + + private static class closedir { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + LibC.C_INT, + LibC.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("closedir"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * extern int closedir(DIR *__dirp) + * } + */ + public static FunctionDescriptor closedir$descriptor() { + return closedir.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * extern int closedir(DIR *__dirp) + * } + */ + public static MethodHandle closedir$handle() { + return closedir.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * extern int closedir(DIR *__dirp) + * } + */ + public static MemorySegment closedir$address() { + return closedir.ADDR; + } + + /** + * {@snippet lang=c : + * extern int closedir(DIR *__dirp) + * } + */ + public static int closedir(MemorySegment __dirp) { + var mh$ = closedir.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("closedir", __dirp); + } + return (int)mh$.invokeExact(__dirp); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class opendir { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + LibC.C_POINTER, + LibC.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("opendir"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * extern DIR *opendir(const char *__name) + * } + */ + public static FunctionDescriptor opendir$descriptor() { + return opendir.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * extern DIR *opendir(const char *__name) + * } + */ + public static MethodHandle opendir$handle() { + return opendir.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * extern DIR *opendir(const char *__name) + * } + */ + public static MemorySegment opendir$address() { + return opendir.ADDR; + } + + /** + * {@snippet lang=c : + * extern DIR *opendir(const char *__name) + * } + */ + public static MemorySegment opendir(MemorySegment __name) { + var mh$ = opendir.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("opendir", __name); + } + return (MemorySegment)mh$.invokeExact(__name); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class readdir { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + LibC.C_POINTER, + LibC.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("readdir"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * extern struct dirent *readdir(DIR *__dirp) + * } + */ + public static FunctionDescriptor readdir$descriptor() { + return readdir.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * extern struct dirent *readdir(DIR *__dirp) + * } + */ + public static MethodHandle readdir$handle() { + return readdir.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * extern struct dirent *readdir(DIR *__dirp) + * } + */ + public static MemorySegment readdir$address() { + return readdir.ADDR; + } + + /** + * {@snippet lang=c : + * extern struct dirent *readdir(DIR *__dirp) + * } + */ + public static MemorySegment readdir(MemorySegment __dirp) { + var mh$ = readdir.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("readdir", __dirp); + } + return (MemorySegment)mh$.invokeExact(__dirp); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class dirfd { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + LibC.C_INT, + LibC.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("dirfd"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * extern int dirfd(DIR *__dirp) + * } + */ + public static FunctionDescriptor dirfd$descriptor() { + return dirfd.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * extern int dirfd(DIR *__dirp) + * } + */ + public static MethodHandle dirfd$handle() { + return dirfd.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * extern int dirfd(DIR *__dirp) + * } + */ + public static MemorySegment dirfd$address() { + return dirfd.ADDR; + } + + /** + * {@snippet lang=c : + * extern int dirfd(DIR *__dirp) + * } + */ + public static int dirfd(MemorySegment __dirp) { + var mh$ = dirfd.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("dirfd", __dirp); + } + return (int)mh$.invokeExact(__dirp); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class chmod { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + LibC.C_INT, + LibC.C_POINTER, + LibC.C_INT + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("chmod"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * extern int chmod(const char *__file, __mode_t __mode) + * } + */ + public static FunctionDescriptor chmod$descriptor() { + return chmod.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * extern int chmod(const char *__file, __mode_t __mode) + * } + */ + public static MethodHandle chmod$handle() { + return chmod.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * extern int chmod(const char *__file, __mode_t __mode) + * } + */ + public static MemorySegment chmod$address() { + return chmod.ADDR; + } + + /** + * {@snippet lang=c : + * extern int chmod(const char *__file, __mode_t __mode) + * } + */ + public static int chmod(MemorySegment __file, int __mode) { + var mh$ = chmod.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("chmod", __file, __mode); + } + return (int)mh$.invokeExact(__file, __mode); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class statx { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + LibC.C_INT, + LibC.C_INT, + LibC.C_POINTER, + LibC.C_INT, + LibC.C_INT, + LibC.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("statx"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int statx(int __dirfd, const char *restrict __path, int __flags, unsigned int __mask, struct statx *restrict __buf) + * } + */ + public static FunctionDescriptor statx$descriptor() { + return statx.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int statx(int __dirfd, const char *restrict __path, int __flags, unsigned int __mask, struct statx *restrict __buf) + * } + */ + public static MethodHandle statx$handle() { + return statx.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int statx(int __dirfd, const char *restrict __path, int __flags, unsigned int __mask, struct statx *restrict __buf) + * } + */ + public static MemorySegment statx$address() { + return statx.ADDR; + } + + /** + * {@snippet lang=c : + * int statx(int __dirfd, const char *restrict __path, int __flags, unsigned int __mask, struct statx *restrict __buf) + * } + */ + public static int statx(int __dirfd, MemorySegment __path, int __flags, int __mask, MemorySegment __buf) { + var mh$ = statx.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("statx", __dirfd, __path, __flags, __mask, __buf); + } + return (int)mh$.invokeExact(__dirfd, __path, __flags, __mask, __buf); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class readlinkat { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + LibC.C_LONG, + LibC.C_INT, + LibC.C_POINTER, + LibC.C_POINTER, + LibC.C_LONG + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("readlinkat"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * extern ssize_t readlinkat(int __fd, const char *restrict __path, char *restrict __buf, size_t __len) + * } + */ + public static FunctionDescriptor readlinkat$descriptor() { + return readlinkat.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * extern ssize_t readlinkat(int __fd, const char *restrict __path, char *restrict __buf, size_t __len) + * } + */ + public static MethodHandle readlinkat$handle() { + return readlinkat.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * extern ssize_t readlinkat(int __fd, const char *restrict __path, char *restrict __buf, size_t __len) + * } + */ + public static MemorySegment readlinkat$address() { + return readlinkat.ADDR; + } + + /** + * {@snippet lang=c : + * extern ssize_t readlinkat(int __fd, const char *restrict __path, char *restrict __buf, size_t __len) + * } + */ + public static long readlinkat(int __fd, MemorySegment __path, MemorySegment __buf, long __len) { + var mh$ = readlinkat.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("readlinkat", __fd, __path, __buf, __len); + } + return (long)mh$.invokeExact(__fd, __path, __buf, __len); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + private static final int S_IFMT = (int)61440L; + /** + * {@snippet lang=c : + * #define S_IFMT 61440 + * } + */ + public static int S_IFMT() { + return S_IFMT; + } + private static final int S_IFDIR = (int)16384L; + /** + * {@snippet lang=c : + * #define S_IFDIR 16384 + * } + */ + public static int S_IFDIR() { + return S_IFDIR; + } + private static final int S_IFLNK = (int)40960L; + /** + * {@snippet lang=c : + * #define S_IFLNK 40960 + * } + */ + public static int S_IFLNK() { + return S_IFLNK; + } + private static final int S_IRUSR = (int)256L; + /** + * {@snippet lang=c : + * #define S_IRUSR 256 + * } + */ + public static int S_IRUSR() { + return S_IRUSR; + } + private static final int S_IWUSR = (int)128L; + /** + * {@snippet lang=c : + * #define S_IWUSR 128 + * } + */ + public static int S_IWUSR() { + return S_IWUSR; + } + private static final int S_IXUSR = (int)64L; + /** + * {@snippet lang=c : + * #define S_IXUSR 64 + * } + */ + public static int S_IXUSR() { + return S_IXUSR; + } + private static final int S_IRGRP = (int)32L; + /** + * {@snippet lang=c : + * #define S_IRGRP 32 + * } + */ + public static int S_IRGRP() { + return S_IRGRP; + } + private static final int S_IWGRP = (int)16L; + /** + * {@snippet lang=c : + * #define S_IWGRP 16 + * } + */ + public static int S_IWGRP() { + return S_IWGRP; + } + private static final int S_IXGRP = (int)8L; + /** + * {@snippet lang=c : + * #define S_IXGRP 8 + * } + */ + public static int S_IXGRP() { + return S_IXGRP; + } + private static final int S_IROTH = (int)4L; + /** + * {@snippet lang=c : + * #define S_IROTH 4 + * } + */ + public static int S_IROTH() { + return S_IROTH; + } + private static final int S_IWOTH = (int)2L; + /** + * {@snippet lang=c : + * #define S_IWOTH 2 + * } + */ + public static int S_IWOTH() { + return S_IWOTH; + } + private static final int S_IXOTH = (int)1L; + /** + * {@snippet lang=c : + * #define S_IXOTH 1 + * } + */ + public static int S_IXOTH() { + return S_IXOTH; + } + private static final int AT_FDCWD = (int)-100L; + /** + * {@snippet lang=c : + * #define AT_FDCWD -100 + * } + */ + public static int AT_FDCWD() { + return AT_FDCWD; + } + private static final int STATX_TYPE = (int)1L; + /** + * {@snippet lang=c : + * #define STATX_TYPE 1 + * } + */ + public static int STATX_TYPE() { + return STATX_TYPE; + } + private static final int STATX_MODE = (int)2L; + /** + * {@snippet lang=c : + * #define STATX_MODE 2 + * } + */ + public static int STATX_MODE() { + return STATX_MODE; + } + private static final int STATX_MTIME = (int)64L; + /** + * {@snippet lang=c : + * #define STATX_MTIME 64 + * } + */ + public static int STATX_MTIME() { + return STATX_MTIME; + } + private static final int STATX_SIZE = (int)512L; + /** + * {@snippet lang=c : + * #define STATX_SIZE 512 + * } + */ + public static int STATX_SIZE() { + return STATX_SIZE; + } +} + diff --git a/resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/dirent.java b/resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/dirent.java new file mode 100644 index 00000000000..6fc9117956f --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/dirent.java @@ -0,0 +1,345 @@ +// Generated by jextract + +package org.linux; + +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 dirent { + * __ino_t d_ino; + * __off_t d_off; + * unsigned short d_reclen; + * unsigned char d_type; + * char d_name[256]; + * } + * } + */ +public class dirent { + + dirent() { + // Should not be called directly + } + + private static final GroupLayout $LAYOUT = MemoryLayout.structLayout( + LibC.C_LONG.withName("d_ino"), + LibC.C_LONG.withName("d_off"), + LibC.C_SHORT.withName("d_reclen"), + LibC.C_CHAR.withName("d_type"), + MemoryLayout.sequenceLayout(256, LibC.C_CHAR).withName("d_name"), + MemoryLayout.paddingLayout(5) + ).withName("dirent"); + + /** + * The layout of this struct + */ + public static final GroupLayout layout() { + return $LAYOUT; + } + + private static final OfLong d_ino$LAYOUT = (OfLong)$LAYOUT.select(groupElement("d_ino")); + + /** + * Layout for field: + * {@snippet lang=c : + * __ino_t d_ino + * } + */ + public static final OfLong d_ino$layout() { + return d_ino$LAYOUT; + } + + private static final long d_ino$OFFSET = $LAYOUT.byteOffset(groupElement("d_ino")); + + /** + * Offset for field: + * {@snippet lang=c : + * __ino_t d_ino + * } + */ + public static final long d_ino$offset() { + return d_ino$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __ino_t d_ino + * } + */ + public static long d_ino(MemorySegment struct) { + return struct.get(d_ino$LAYOUT, d_ino$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __ino_t d_ino + * } + */ + public static void d_ino(MemorySegment struct, long fieldValue) { + struct.set(d_ino$LAYOUT, d_ino$OFFSET, fieldValue); + } + + private static final OfLong d_off$LAYOUT = (OfLong)$LAYOUT.select(groupElement("d_off")); + + /** + * Layout for field: + * {@snippet lang=c : + * __off_t d_off + * } + */ + public static final OfLong d_off$layout() { + return d_off$LAYOUT; + } + + private static final long d_off$OFFSET = $LAYOUT.byteOffset(groupElement("d_off")); + + /** + * Offset for field: + * {@snippet lang=c : + * __off_t d_off + * } + */ + public static final long d_off$offset() { + return d_off$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __off_t d_off + * } + */ + public static long d_off(MemorySegment struct) { + return struct.get(d_off$LAYOUT, d_off$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __off_t d_off + * } + */ + public static void d_off(MemorySegment struct, long fieldValue) { + struct.set(d_off$LAYOUT, d_off$OFFSET, fieldValue); + } + + private static final OfShort d_reclen$LAYOUT = (OfShort)$LAYOUT.select(groupElement("d_reclen")); + + /** + * Layout for field: + * {@snippet lang=c : + * unsigned short d_reclen + * } + */ + public static final OfShort d_reclen$layout() { + return d_reclen$LAYOUT; + } + + private static final long d_reclen$OFFSET = $LAYOUT.byteOffset(groupElement("d_reclen")); + + /** + * Offset for field: + * {@snippet lang=c : + * unsigned short d_reclen + * } + */ + public static final long d_reclen$offset() { + return d_reclen$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * unsigned short d_reclen + * } + */ + public static short d_reclen(MemorySegment struct) { + return struct.get(d_reclen$LAYOUT, d_reclen$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * unsigned short d_reclen + * } + */ + public static void d_reclen(MemorySegment struct, short fieldValue) { + struct.set(d_reclen$LAYOUT, d_reclen$OFFSET, fieldValue); + } + + private static final OfByte d_type$LAYOUT = (OfByte)$LAYOUT.select(groupElement("d_type")); + + /** + * Layout for field: + * {@snippet lang=c : + * unsigned char d_type + * } + */ + public static final OfByte d_type$layout() { + return d_type$LAYOUT; + } + + private static final long d_type$OFFSET = $LAYOUT.byteOffset(groupElement("d_type")); + + /** + * Offset for field: + * {@snippet lang=c : + * unsigned char d_type + * } + */ + public static final long d_type$offset() { + return d_type$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * unsigned char d_type + * } + */ + public static byte d_type(MemorySegment struct) { + return struct.get(d_type$LAYOUT, d_type$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * unsigned char d_type + * } + */ + public static void d_type(MemorySegment struct, byte fieldValue) { + struct.set(d_type$LAYOUT, d_type$OFFSET, fieldValue); + } + + private static final SequenceLayout d_name$LAYOUT = (SequenceLayout)$LAYOUT.select(groupElement("d_name")); + + /** + * Layout for field: + * {@snippet lang=c : + * char d_name[256] + * } + */ + public static final SequenceLayout d_name$layout() { + return d_name$LAYOUT; + } + + private static final long d_name$OFFSET = $LAYOUT.byteOffset(groupElement("d_name")); + + /** + * Offset for field: + * {@snippet lang=c : + * char d_name[256] + * } + */ + public static final long d_name$offset() { + return d_name$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * char d_name[256] + * } + */ + public static MemorySegment d_name(MemorySegment struct) { + return struct.asSlice(d_name$OFFSET, d_name$LAYOUT.byteSize()); + } + + /** + * Setter for field: + * {@snippet lang=c : + * char d_name[256] + * } + */ + public static void d_name(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, d_name$OFFSET, d_name$LAYOUT.byteSize()); + } + + private static long[] d_name$DIMS = { 256 }; + + /** + * Dimensions for array field: + * {@snippet lang=c : + * char d_name[256] + * } + */ + public static long[] d_name$dimensions() { + return d_name$DIMS; + } + private static final VarHandle d_name$ELEM_HANDLE = d_name$LAYOUT.varHandle(sequenceElement()); + + /** + * Indexed getter for field: + * {@snippet lang=c : + * char d_name[256] + * } + */ + public static byte d_name(MemorySegment struct, long index0) { + return (byte)d_name$ELEM_HANDLE.get(struct, d_name$OFFSET, index0); + } + + /** + * Indexed setter for field: + * {@snippet lang=c : + * char d_name[256] + * } + */ + public static void d_name(MemorySegment struct, long index0, byte fieldValue) { + d_name$ELEM_HANDLE.set(struct, d_name$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-gen/org/linux/statx.java b/resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/statx.java new file mode 100644 index 00000000000..374da0a6f57 --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/statx.java @@ -0,0 +1,1606 @@ +// Generated by jextract + +package org.linux; + +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 statx { + * __u32 stx_mask; + * __u32 stx_blksize; + * __u64 stx_attributes; + * __u32 stx_nlink; + * __u32 stx_uid; + * __u32 stx_gid; + * __u16 stx_mode; + * __u16 __spare0[1]; + * __u64 stx_ino; + * __u64 stx_size; + * __u64 stx_blocks; + * __u64 stx_attributes_mask; + * struct statx_timestamp stx_atime; + * struct statx_timestamp stx_btime; + * struct statx_timestamp stx_ctime; + * struct statx_timestamp stx_mtime; + * __u32 stx_rdev_major; + * __u32 stx_rdev_minor; + * __u32 stx_dev_major; + * __u32 stx_dev_minor; + * __u64 stx_mnt_id; + * __u32 stx_dio_mem_align; + * __u32 stx_dio_offset_align; + * __u64 stx_subvol; + * __u32 stx_atomic_write_unit_min; + * __u32 stx_atomic_write_unit_max; + * __u32 stx_atomic_write_segments_max; + * __u32 stx_dio_read_offset_align; + * __u32 stx_atomic_write_unit_max_opt; + * __u32 __spare2[1]; + * __u64 __spare3[8]; + * } + * } + */ +public class statx { + + statx() { + // Should not be called directly + } + + private static final GroupLayout $LAYOUT = MemoryLayout.structLayout( + LibC.C_INT.withName("stx_mask"), + LibC.C_INT.withName("stx_blksize"), + LibC.C_LONG_LONG.withName("stx_attributes"), + LibC.C_INT.withName("stx_nlink"), + LibC.C_INT.withName("stx_uid"), + LibC.C_INT.withName("stx_gid"), + LibC.C_SHORT.withName("stx_mode"), + MemoryLayout.sequenceLayout(1, LibC.C_SHORT).withName("__spare0"), + LibC.C_LONG_LONG.withName("stx_ino"), + LibC.C_LONG_LONG.withName("stx_size"), + LibC.C_LONG_LONG.withName("stx_blocks"), + LibC.C_LONG_LONG.withName("stx_attributes_mask"), + statx_timestamp.layout().withName("stx_atime"), + statx_timestamp.layout().withName("stx_btime"), + statx_timestamp.layout().withName("stx_ctime"), + statx_timestamp.layout().withName("stx_mtime"), + LibC.C_INT.withName("stx_rdev_major"), + LibC.C_INT.withName("stx_rdev_minor"), + LibC.C_INT.withName("stx_dev_major"), + LibC.C_INT.withName("stx_dev_minor"), + LibC.C_LONG_LONG.withName("stx_mnt_id"), + LibC.C_INT.withName("stx_dio_mem_align"), + LibC.C_INT.withName("stx_dio_offset_align"), + LibC.C_LONG_LONG.withName("stx_subvol"), + LibC.C_INT.withName("stx_atomic_write_unit_min"), + LibC.C_INT.withName("stx_atomic_write_unit_max"), + LibC.C_INT.withName("stx_atomic_write_segments_max"), + LibC.C_INT.withName("stx_dio_read_offset_align"), + LibC.C_INT.withName("stx_atomic_write_unit_max_opt"), + MemoryLayout.sequenceLayout(1, LibC.C_INT).withName("__spare2"), + MemoryLayout.sequenceLayout(8, LibC.C_LONG_LONG).withName("__spare3") + ).withName("statx"); + + /** + * The layout of this struct + */ + public static final GroupLayout layout() { + return $LAYOUT; + } + + private static final OfInt stx_mask$LAYOUT = (OfInt)$LAYOUT.select(groupElement("stx_mask")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 stx_mask + * } + */ + public static final OfInt stx_mask$layout() { + return stx_mask$LAYOUT; + } + + private static final long stx_mask$OFFSET = $LAYOUT.byteOffset(groupElement("stx_mask")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 stx_mask + * } + */ + public static final long stx_mask$offset() { + return stx_mask$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 stx_mask + * } + */ + public static int stx_mask(MemorySegment struct) { + return struct.get(stx_mask$LAYOUT, stx_mask$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 stx_mask + * } + */ + public static void stx_mask(MemorySegment struct, int fieldValue) { + struct.set(stx_mask$LAYOUT, stx_mask$OFFSET, fieldValue); + } + + private static final OfInt stx_blksize$LAYOUT = (OfInt)$LAYOUT.select(groupElement("stx_blksize")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 stx_blksize + * } + */ + public static final OfInt stx_blksize$layout() { + return stx_blksize$LAYOUT; + } + + private static final long stx_blksize$OFFSET = $LAYOUT.byteOffset(groupElement("stx_blksize")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 stx_blksize + * } + */ + public static final long stx_blksize$offset() { + return stx_blksize$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 stx_blksize + * } + */ + public static int stx_blksize(MemorySegment struct) { + return struct.get(stx_blksize$LAYOUT, stx_blksize$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 stx_blksize + * } + */ + public static void stx_blksize(MemorySegment struct, int fieldValue) { + struct.set(stx_blksize$LAYOUT, stx_blksize$OFFSET, fieldValue); + } + + private static final OfLong stx_attributes$LAYOUT = (OfLong)$LAYOUT.select(groupElement("stx_attributes")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u64 stx_attributes + * } + */ + public static final OfLong stx_attributes$layout() { + return stx_attributes$LAYOUT; + } + + private static final long stx_attributes$OFFSET = $LAYOUT.byteOffset(groupElement("stx_attributes")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u64 stx_attributes + * } + */ + public static final long stx_attributes$offset() { + return stx_attributes$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u64 stx_attributes + * } + */ + public static long stx_attributes(MemorySegment struct) { + return struct.get(stx_attributes$LAYOUT, stx_attributes$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u64 stx_attributes + * } + */ + public static void stx_attributes(MemorySegment struct, long fieldValue) { + struct.set(stx_attributes$LAYOUT, stx_attributes$OFFSET, fieldValue); + } + + private static final OfInt stx_nlink$LAYOUT = (OfInt)$LAYOUT.select(groupElement("stx_nlink")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 stx_nlink + * } + */ + public static final OfInt stx_nlink$layout() { + return stx_nlink$LAYOUT; + } + + private static final long stx_nlink$OFFSET = $LAYOUT.byteOffset(groupElement("stx_nlink")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 stx_nlink + * } + */ + public static final long stx_nlink$offset() { + return stx_nlink$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 stx_nlink + * } + */ + public static int stx_nlink(MemorySegment struct) { + return struct.get(stx_nlink$LAYOUT, stx_nlink$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 stx_nlink + * } + */ + public static void stx_nlink(MemorySegment struct, int fieldValue) { + struct.set(stx_nlink$LAYOUT, stx_nlink$OFFSET, fieldValue); + } + + private static final OfInt stx_uid$LAYOUT = (OfInt)$LAYOUT.select(groupElement("stx_uid")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 stx_uid + * } + */ + public static final OfInt stx_uid$layout() { + return stx_uid$LAYOUT; + } + + private static final long stx_uid$OFFSET = $LAYOUT.byteOffset(groupElement("stx_uid")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 stx_uid + * } + */ + public static final long stx_uid$offset() { + return stx_uid$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 stx_uid + * } + */ + public static int stx_uid(MemorySegment struct) { + return struct.get(stx_uid$LAYOUT, stx_uid$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 stx_uid + * } + */ + public static void stx_uid(MemorySegment struct, int fieldValue) { + struct.set(stx_uid$LAYOUT, stx_uid$OFFSET, fieldValue); + } + + private static final OfInt stx_gid$LAYOUT = (OfInt)$LAYOUT.select(groupElement("stx_gid")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 stx_gid + * } + */ + public static final OfInt stx_gid$layout() { + return stx_gid$LAYOUT; + } + + private static final long stx_gid$OFFSET = $LAYOUT.byteOffset(groupElement("stx_gid")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 stx_gid + * } + */ + public static final long stx_gid$offset() { + return stx_gid$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 stx_gid + * } + */ + public static int stx_gid(MemorySegment struct) { + return struct.get(stx_gid$LAYOUT, stx_gid$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 stx_gid + * } + */ + public static void stx_gid(MemorySegment struct, int fieldValue) { + struct.set(stx_gid$LAYOUT, stx_gid$OFFSET, fieldValue); + } + + private static final OfShort stx_mode$LAYOUT = (OfShort)$LAYOUT.select(groupElement("stx_mode")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u16 stx_mode + * } + */ + public static final OfShort stx_mode$layout() { + return stx_mode$LAYOUT; + } + + private static final long stx_mode$OFFSET = $LAYOUT.byteOffset(groupElement("stx_mode")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u16 stx_mode + * } + */ + public static final long stx_mode$offset() { + return stx_mode$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u16 stx_mode + * } + */ + public static short stx_mode(MemorySegment struct) { + return struct.get(stx_mode$LAYOUT, stx_mode$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u16 stx_mode + * } + */ + public static void stx_mode(MemorySegment struct, short fieldValue) { + struct.set(stx_mode$LAYOUT, stx_mode$OFFSET, fieldValue); + } + + private static final SequenceLayout __spare0$LAYOUT = (SequenceLayout)$LAYOUT.select(groupElement("__spare0")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u16 __spare0[1] + * } + */ + public static final SequenceLayout __spare0$layout() { + return __spare0$LAYOUT; + } + + private static final long __spare0$OFFSET = $LAYOUT.byteOffset(groupElement("__spare0")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u16 __spare0[1] + * } + */ + public static final long __spare0$offset() { + return __spare0$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u16 __spare0[1] + * } + */ + public static MemorySegment __spare0(MemorySegment struct) { + return struct.asSlice(__spare0$OFFSET, __spare0$LAYOUT.byteSize()); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u16 __spare0[1] + * } + */ + public static void __spare0(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, __spare0$OFFSET, __spare0$LAYOUT.byteSize()); + } + + private static long[] __spare0$DIMS = { 1 }; + + /** + * Dimensions for array field: + * {@snippet lang=c : + * __u16 __spare0[1] + * } + */ + public static long[] __spare0$dimensions() { + return __spare0$DIMS; + } + private static final VarHandle __spare0$ELEM_HANDLE = __spare0$LAYOUT.varHandle(sequenceElement()); + + /** + * Indexed getter for field: + * {@snippet lang=c : + * __u16 __spare0[1] + * } + */ + public static short __spare0(MemorySegment struct, long index0) { + return (short)__spare0$ELEM_HANDLE.get(struct, __spare0$OFFSET, index0); + } + + /** + * Indexed setter for field: + * {@snippet lang=c : + * __u16 __spare0[1] + * } + */ + public static void __spare0(MemorySegment struct, long index0, short fieldValue) { + __spare0$ELEM_HANDLE.set(struct, __spare0$OFFSET, index0, fieldValue); + } + + private static final OfLong stx_ino$LAYOUT = (OfLong)$LAYOUT.select(groupElement("stx_ino")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u64 stx_ino + * } + */ + public static final OfLong stx_ino$layout() { + return stx_ino$LAYOUT; + } + + private static final long stx_ino$OFFSET = $LAYOUT.byteOffset(groupElement("stx_ino")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u64 stx_ino + * } + */ + public static final long stx_ino$offset() { + return stx_ino$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u64 stx_ino + * } + */ + public static long stx_ino(MemorySegment struct) { + return struct.get(stx_ino$LAYOUT, stx_ino$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u64 stx_ino + * } + */ + public static void stx_ino(MemorySegment struct, long fieldValue) { + struct.set(stx_ino$LAYOUT, stx_ino$OFFSET, fieldValue); + } + + private static final OfLong stx_size$LAYOUT = (OfLong)$LAYOUT.select(groupElement("stx_size")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u64 stx_size + * } + */ + public static final OfLong stx_size$layout() { + return stx_size$LAYOUT; + } + + private static final long stx_size$OFFSET = $LAYOUT.byteOffset(groupElement("stx_size")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u64 stx_size + * } + */ + public static final long stx_size$offset() { + return stx_size$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u64 stx_size + * } + */ + public static long stx_size(MemorySegment struct) { + return struct.get(stx_size$LAYOUT, stx_size$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u64 stx_size + * } + */ + public static void stx_size(MemorySegment struct, long fieldValue) { + struct.set(stx_size$LAYOUT, stx_size$OFFSET, fieldValue); + } + + private static final OfLong stx_blocks$LAYOUT = (OfLong)$LAYOUT.select(groupElement("stx_blocks")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u64 stx_blocks + * } + */ + public static final OfLong stx_blocks$layout() { + return stx_blocks$LAYOUT; + } + + private static final long stx_blocks$OFFSET = $LAYOUT.byteOffset(groupElement("stx_blocks")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u64 stx_blocks + * } + */ + public static final long stx_blocks$offset() { + return stx_blocks$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u64 stx_blocks + * } + */ + public static long stx_blocks(MemorySegment struct) { + return struct.get(stx_blocks$LAYOUT, stx_blocks$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u64 stx_blocks + * } + */ + public static void stx_blocks(MemorySegment struct, long fieldValue) { + struct.set(stx_blocks$LAYOUT, stx_blocks$OFFSET, fieldValue); + } + + private static final OfLong stx_attributes_mask$LAYOUT = (OfLong)$LAYOUT.select(groupElement("stx_attributes_mask")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u64 stx_attributes_mask + * } + */ + public static final OfLong stx_attributes_mask$layout() { + return stx_attributes_mask$LAYOUT; + } + + private static final long stx_attributes_mask$OFFSET = $LAYOUT.byteOffset(groupElement("stx_attributes_mask")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u64 stx_attributes_mask + * } + */ + public static final long stx_attributes_mask$offset() { + return stx_attributes_mask$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u64 stx_attributes_mask + * } + */ + public static long stx_attributes_mask(MemorySegment struct) { + return struct.get(stx_attributes_mask$LAYOUT, stx_attributes_mask$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u64 stx_attributes_mask + * } + */ + public static void stx_attributes_mask(MemorySegment struct, long fieldValue) { + struct.set(stx_attributes_mask$LAYOUT, stx_attributes_mask$OFFSET, fieldValue); + } + + private static final GroupLayout stx_atime$LAYOUT = (GroupLayout)$LAYOUT.select(groupElement("stx_atime")); + + /** + * Layout for field: + * {@snippet lang=c : + * struct statx_timestamp stx_atime + * } + */ + public static final GroupLayout stx_atime$layout() { + return stx_atime$LAYOUT; + } + + private static final long stx_atime$OFFSET = $LAYOUT.byteOffset(groupElement("stx_atime")); + + /** + * Offset for field: + * {@snippet lang=c : + * struct statx_timestamp stx_atime + * } + */ + public static final long stx_atime$offset() { + return stx_atime$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * struct statx_timestamp stx_atime + * } + */ + public static MemorySegment stx_atime(MemorySegment struct) { + return struct.asSlice(stx_atime$OFFSET, stx_atime$LAYOUT.byteSize()); + } + + /** + * Setter for field: + * {@snippet lang=c : + * struct statx_timestamp stx_atime + * } + */ + public static void stx_atime(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, stx_atime$OFFSET, stx_atime$LAYOUT.byteSize()); + } + + private static final GroupLayout stx_btime$LAYOUT = (GroupLayout)$LAYOUT.select(groupElement("stx_btime")); + + /** + * Layout for field: + * {@snippet lang=c : + * struct statx_timestamp stx_btime + * } + */ + public static final GroupLayout stx_btime$layout() { + return stx_btime$LAYOUT; + } + + private static final long stx_btime$OFFSET = $LAYOUT.byteOffset(groupElement("stx_btime")); + + /** + * Offset for field: + * {@snippet lang=c : + * struct statx_timestamp stx_btime + * } + */ + public static final long stx_btime$offset() { + return stx_btime$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * struct statx_timestamp stx_btime + * } + */ + public static MemorySegment stx_btime(MemorySegment struct) { + return struct.asSlice(stx_btime$OFFSET, stx_btime$LAYOUT.byteSize()); + } + + /** + * Setter for field: + * {@snippet lang=c : + * struct statx_timestamp stx_btime + * } + */ + public static void stx_btime(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, stx_btime$OFFSET, stx_btime$LAYOUT.byteSize()); + } + + private static final GroupLayout stx_ctime$LAYOUT = (GroupLayout)$LAYOUT.select(groupElement("stx_ctime")); + + /** + * Layout for field: + * {@snippet lang=c : + * struct statx_timestamp stx_ctime + * } + */ + public static final GroupLayout stx_ctime$layout() { + return stx_ctime$LAYOUT; + } + + private static final long stx_ctime$OFFSET = $LAYOUT.byteOffset(groupElement("stx_ctime")); + + /** + * Offset for field: + * {@snippet lang=c : + * struct statx_timestamp stx_ctime + * } + */ + public static final long stx_ctime$offset() { + return stx_ctime$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * struct statx_timestamp stx_ctime + * } + */ + public static MemorySegment stx_ctime(MemorySegment struct) { + return struct.asSlice(stx_ctime$OFFSET, stx_ctime$LAYOUT.byteSize()); + } + + /** + * Setter for field: + * {@snippet lang=c : + * struct statx_timestamp stx_ctime + * } + */ + public static void stx_ctime(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, stx_ctime$OFFSET, stx_ctime$LAYOUT.byteSize()); + } + + private static final GroupLayout stx_mtime$LAYOUT = (GroupLayout)$LAYOUT.select(groupElement("stx_mtime")); + + /** + * Layout for field: + * {@snippet lang=c : + * struct statx_timestamp stx_mtime + * } + */ + public static final GroupLayout stx_mtime$layout() { + return stx_mtime$LAYOUT; + } + + private static final long stx_mtime$OFFSET = $LAYOUT.byteOffset(groupElement("stx_mtime")); + + /** + * Offset for field: + * {@snippet lang=c : + * struct statx_timestamp stx_mtime + * } + */ + public static final long stx_mtime$offset() { + return stx_mtime$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * struct statx_timestamp stx_mtime + * } + */ + public static MemorySegment stx_mtime(MemorySegment struct) { + return struct.asSlice(stx_mtime$OFFSET, stx_mtime$LAYOUT.byteSize()); + } + + /** + * Setter for field: + * {@snippet lang=c : + * struct statx_timestamp stx_mtime + * } + */ + public static void stx_mtime(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, stx_mtime$OFFSET, stx_mtime$LAYOUT.byteSize()); + } + + private static final OfInt stx_rdev_major$LAYOUT = (OfInt)$LAYOUT.select(groupElement("stx_rdev_major")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 stx_rdev_major + * } + */ + public static final OfInt stx_rdev_major$layout() { + return stx_rdev_major$LAYOUT; + } + + private static final long stx_rdev_major$OFFSET = $LAYOUT.byteOffset(groupElement("stx_rdev_major")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 stx_rdev_major + * } + */ + public static final long stx_rdev_major$offset() { + return stx_rdev_major$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 stx_rdev_major + * } + */ + public static int stx_rdev_major(MemorySegment struct) { + return struct.get(stx_rdev_major$LAYOUT, stx_rdev_major$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 stx_rdev_major + * } + */ + public static void stx_rdev_major(MemorySegment struct, int fieldValue) { + struct.set(stx_rdev_major$LAYOUT, stx_rdev_major$OFFSET, fieldValue); + } + + private static final OfInt stx_rdev_minor$LAYOUT = (OfInt)$LAYOUT.select(groupElement("stx_rdev_minor")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 stx_rdev_minor + * } + */ + public static final OfInt stx_rdev_minor$layout() { + return stx_rdev_minor$LAYOUT; + } + + private static final long stx_rdev_minor$OFFSET = $LAYOUT.byteOffset(groupElement("stx_rdev_minor")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 stx_rdev_minor + * } + */ + public static final long stx_rdev_minor$offset() { + return stx_rdev_minor$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 stx_rdev_minor + * } + */ + public static int stx_rdev_minor(MemorySegment struct) { + return struct.get(stx_rdev_minor$LAYOUT, stx_rdev_minor$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 stx_rdev_minor + * } + */ + public static void stx_rdev_minor(MemorySegment struct, int fieldValue) { + struct.set(stx_rdev_minor$LAYOUT, stx_rdev_minor$OFFSET, fieldValue); + } + + private static final OfInt stx_dev_major$LAYOUT = (OfInt)$LAYOUT.select(groupElement("stx_dev_major")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 stx_dev_major + * } + */ + public static final OfInt stx_dev_major$layout() { + return stx_dev_major$LAYOUT; + } + + private static final long stx_dev_major$OFFSET = $LAYOUT.byteOffset(groupElement("stx_dev_major")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 stx_dev_major + * } + */ + public static final long stx_dev_major$offset() { + return stx_dev_major$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 stx_dev_major + * } + */ + public static int stx_dev_major(MemorySegment struct) { + return struct.get(stx_dev_major$LAYOUT, stx_dev_major$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 stx_dev_major + * } + */ + public static void stx_dev_major(MemorySegment struct, int fieldValue) { + struct.set(stx_dev_major$LAYOUT, stx_dev_major$OFFSET, fieldValue); + } + + private static final OfInt stx_dev_minor$LAYOUT = (OfInt)$LAYOUT.select(groupElement("stx_dev_minor")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 stx_dev_minor + * } + */ + public static final OfInt stx_dev_minor$layout() { + return stx_dev_minor$LAYOUT; + } + + private static final long stx_dev_minor$OFFSET = $LAYOUT.byteOffset(groupElement("stx_dev_minor")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 stx_dev_minor + * } + */ + public static final long stx_dev_minor$offset() { + return stx_dev_minor$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 stx_dev_minor + * } + */ + public static int stx_dev_minor(MemorySegment struct) { + return struct.get(stx_dev_minor$LAYOUT, stx_dev_minor$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 stx_dev_minor + * } + */ + public static void stx_dev_minor(MemorySegment struct, int fieldValue) { + struct.set(stx_dev_minor$LAYOUT, stx_dev_minor$OFFSET, fieldValue); + } + + private static final OfLong stx_mnt_id$LAYOUT = (OfLong)$LAYOUT.select(groupElement("stx_mnt_id")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u64 stx_mnt_id + * } + */ + public static final OfLong stx_mnt_id$layout() { + return stx_mnt_id$LAYOUT; + } + + private static final long stx_mnt_id$OFFSET = $LAYOUT.byteOffset(groupElement("stx_mnt_id")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u64 stx_mnt_id + * } + */ + public static final long stx_mnt_id$offset() { + return stx_mnt_id$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u64 stx_mnt_id + * } + */ + public static long stx_mnt_id(MemorySegment struct) { + return struct.get(stx_mnt_id$LAYOUT, stx_mnt_id$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u64 stx_mnt_id + * } + */ + public static void stx_mnt_id(MemorySegment struct, long fieldValue) { + struct.set(stx_mnt_id$LAYOUT, stx_mnt_id$OFFSET, fieldValue); + } + + private static final OfInt stx_dio_mem_align$LAYOUT = (OfInt)$LAYOUT.select(groupElement("stx_dio_mem_align")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 stx_dio_mem_align + * } + */ + public static final OfInt stx_dio_mem_align$layout() { + return stx_dio_mem_align$LAYOUT; + } + + private static final long stx_dio_mem_align$OFFSET = $LAYOUT.byteOffset(groupElement("stx_dio_mem_align")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 stx_dio_mem_align + * } + */ + public static final long stx_dio_mem_align$offset() { + return stx_dio_mem_align$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 stx_dio_mem_align + * } + */ + public static int stx_dio_mem_align(MemorySegment struct) { + return struct.get(stx_dio_mem_align$LAYOUT, stx_dio_mem_align$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 stx_dio_mem_align + * } + */ + public static void stx_dio_mem_align(MemorySegment struct, int fieldValue) { + struct.set(stx_dio_mem_align$LAYOUT, stx_dio_mem_align$OFFSET, fieldValue); + } + + private static final OfInt stx_dio_offset_align$LAYOUT = (OfInt)$LAYOUT.select(groupElement("stx_dio_offset_align")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 stx_dio_offset_align + * } + */ + public static final OfInt stx_dio_offset_align$layout() { + return stx_dio_offset_align$LAYOUT; + } + + private static final long stx_dio_offset_align$OFFSET = $LAYOUT.byteOffset(groupElement("stx_dio_offset_align")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 stx_dio_offset_align + * } + */ + public static final long stx_dio_offset_align$offset() { + return stx_dio_offset_align$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 stx_dio_offset_align + * } + */ + public static int stx_dio_offset_align(MemorySegment struct) { + return struct.get(stx_dio_offset_align$LAYOUT, stx_dio_offset_align$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 stx_dio_offset_align + * } + */ + public static void stx_dio_offset_align(MemorySegment struct, int fieldValue) { + struct.set(stx_dio_offset_align$LAYOUT, stx_dio_offset_align$OFFSET, fieldValue); + } + + private static final OfLong stx_subvol$LAYOUT = (OfLong)$LAYOUT.select(groupElement("stx_subvol")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u64 stx_subvol + * } + */ + public static final OfLong stx_subvol$layout() { + return stx_subvol$LAYOUT; + } + + private static final long stx_subvol$OFFSET = $LAYOUT.byteOffset(groupElement("stx_subvol")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u64 stx_subvol + * } + */ + public static final long stx_subvol$offset() { + return stx_subvol$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u64 stx_subvol + * } + */ + public static long stx_subvol(MemorySegment struct) { + return struct.get(stx_subvol$LAYOUT, stx_subvol$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u64 stx_subvol + * } + */ + public static void stx_subvol(MemorySegment struct, long fieldValue) { + struct.set(stx_subvol$LAYOUT, stx_subvol$OFFSET, fieldValue); + } + + private static final OfInt stx_atomic_write_unit_min$LAYOUT = (OfInt)$LAYOUT.select(groupElement("stx_atomic_write_unit_min")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 stx_atomic_write_unit_min + * } + */ + public static final OfInt stx_atomic_write_unit_min$layout() { + return stx_atomic_write_unit_min$LAYOUT; + } + + private static final long stx_atomic_write_unit_min$OFFSET = $LAYOUT.byteOffset(groupElement("stx_atomic_write_unit_min")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 stx_atomic_write_unit_min + * } + */ + public static final long stx_atomic_write_unit_min$offset() { + return stx_atomic_write_unit_min$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 stx_atomic_write_unit_min + * } + */ + public static int stx_atomic_write_unit_min(MemorySegment struct) { + return struct.get(stx_atomic_write_unit_min$LAYOUT, stx_atomic_write_unit_min$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 stx_atomic_write_unit_min + * } + */ + public static void stx_atomic_write_unit_min(MemorySegment struct, int fieldValue) { + struct.set(stx_atomic_write_unit_min$LAYOUT, stx_atomic_write_unit_min$OFFSET, fieldValue); + } + + private static final OfInt stx_atomic_write_unit_max$LAYOUT = (OfInt)$LAYOUT.select(groupElement("stx_atomic_write_unit_max")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 stx_atomic_write_unit_max + * } + */ + public static final OfInt stx_atomic_write_unit_max$layout() { + return stx_atomic_write_unit_max$LAYOUT; + } + + private static final long stx_atomic_write_unit_max$OFFSET = $LAYOUT.byteOffset(groupElement("stx_atomic_write_unit_max")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 stx_atomic_write_unit_max + * } + */ + public static final long stx_atomic_write_unit_max$offset() { + return stx_atomic_write_unit_max$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 stx_atomic_write_unit_max + * } + */ + public static int stx_atomic_write_unit_max(MemorySegment struct) { + return struct.get(stx_atomic_write_unit_max$LAYOUT, stx_atomic_write_unit_max$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 stx_atomic_write_unit_max + * } + */ + public static void stx_atomic_write_unit_max(MemorySegment struct, int fieldValue) { + struct.set(stx_atomic_write_unit_max$LAYOUT, stx_atomic_write_unit_max$OFFSET, fieldValue); + } + + private static final OfInt stx_atomic_write_segments_max$LAYOUT = (OfInt)$LAYOUT.select(groupElement("stx_atomic_write_segments_max")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 stx_atomic_write_segments_max + * } + */ + public static final OfInt stx_atomic_write_segments_max$layout() { + return stx_atomic_write_segments_max$LAYOUT; + } + + private static final long stx_atomic_write_segments_max$OFFSET = $LAYOUT.byteOffset(groupElement("stx_atomic_write_segments_max")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 stx_atomic_write_segments_max + * } + */ + public static final long stx_atomic_write_segments_max$offset() { + return stx_atomic_write_segments_max$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 stx_atomic_write_segments_max + * } + */ + public static int stx_atomic_write_segments_max(MemorySegment struct) { + return struct.get(stx_atomic_write_segments_max$LAYOUT, stx_atomic_write_segments_max$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 stx_atomic_write_segments_max + * } + */ + public static void stx_atomic_write_segments_max(MemorySegment struct, int fieldValue) { + struct.set(stx_atomic_write_segments_max$LAYOUT, stx_atomic_write_segments_max$OFFSET, fieldValue); + } + + private static final OfInt stx_dio_read_offset_align$LAYOUT = (OfInt)$LAYOUT.select(groupElement("stx_dio_read_offset_align")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 stx_dio_read_offset_align + * } + */ + public static final OfInt stx_dio_read_offset_align$layout() { + return stx_dio_read_offset_align$LAYOUT; + } + + private static final long stx_dio_read_offset_align$OFFSET = $LAYOUT.byteOffset(groupElement("stx_dio_read_offset_align")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 stx_dio_read_offset_align + * } + */ + public static final long stx_dio_read_offset_align$offset() { + return stx_dio_read_offset_align$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 stx_dio_read_offset_align + * } + */ + public static int stx_dio_read_offset_align(MemorySegment struct) { + return struct.get(stx_dio_read_offset_align$LAYOUT, stx_dio_read_offset_align$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 stx_dio_read_offset_align + * } + */ + public static void stx_dio_read_offset_align(MemorySegment struct, int fieldValue) { + struct.set(stx_dio_read_offset_align$LAYOUT, stx_dio_read_offset_align$OFFSET, fieldValue); + } + + private static final OfInt stx_atomic_write_unit_max_opt$LAYOUT = (OfInt)$LAYOUT.select(groupElement("stx_atomic_write_unit_max_opt")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 stx_atomic_write_unit_max_opt + * } + */ + public static final OfInt stx_atomic_write_unit_max_opt$layout() { + return stx_atomic_write_unit_max_opt$LAYOUT; + } + + private static final long stx_atomic_write_unit_max_opt$OFFSET = $LAYOUT.byteOffset(groupElement("stx_atomic_write_unit_max_opt")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 stx_atomic_write_unit_max_opt + * } + */ + public static final long stx_atomic_write_unit_max_opt$offset() { + return stx_atomic_write_unit_max_opt$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 stx_atomic_write_unit_max_opt + * } + */ + public static int stx_atomic_write_unit_max_opt(MemorySegment struct) { + return struct.get(stx_atomic_write_unit_max_opt$LAYOUT, stx_atomic_write_unit_max_opt$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 stx_atomic_write_unit_max_opt + * } + */ + public static void stx_atomic_write_unit_max_opt(MemorySegment struct, int fieldValue) { + struct.set(stx_atomic_write_unit_max_opt$LAYOUT, stx_atomic_write_unit_max_opt$OFFSET, fieldValue); + } + + private static final SequenceLayout __spare2$LAYOUT = (SequenceLayout)$LAYOUT.select(groupElement("__spare2")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 __spare2[1] + * } + */ + public static final SequenceLayout __spare2$layout() { + return __spare2$LAYOUT; + } + + private static final long __spare2$OFFSET = $LAYOUT.byteOffset(groupElement("__spare2")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 __spare2[1] + * } + */ + public static final long __spare2$offset() { + return __spare2$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 __spare2[1] + * } + */ + public static MemorySegment __spare2(MemorySegment struct) { + return struct.asSlice(__spare2$OFFSET, __spare2$LAYOUT.byteSize()); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 __spare2[1] + * } + */ + public static void __spare2(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, __spare2$OFFSET, __spare2$LAYOUT.byteSize()); + } + + private static long[] __spare2$DIMS = { 1 }; + + /** + * Dimensions for array field: + * {@snippet lang=c : + * __u32 __spare2[1] + * } + */ + public static long[] __spare2$dimensions() { + return __spare2$DIMS; + } + private static final VarHandle __spare2$ELEM_HANDLE = __spare2$LAYOUT.varHandle(sequenceElement()); + + /** + * Indexed getter for field: + * {@snippet lang=c : + * __u32 __spare2[1] + * } + */ + public static int __spare2(MemorySegment struct, long index0) { + return (int)__spare2$ELEM_HANDLE.get(struct, __spare2$OFFSET, index0); + } + + /** + * Indexed setter for field: + * {@snippet lang=c : + * __u32 __spare2[1] + * } + */ + public static void __spare2(MemorySegment struct, long index0, int fieldValue) { + __spare2$ELEM_HANDLE.set(struct, __spare2$OFFSET, index0, fieldValue); + } + + private static final SequenceLayout __spare3$LAYOUT = (SequenceLayout)$LAYOUT.select(groupElement("__spare3")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u64 __spare3[8] + * } + */ + public static final SequenceLayout __spare3$layout() { + return __spare3$LAYOUT; + } + + private static final long __spare3$OFFSET = $LAYOUT.byteOffset(groupElement("__spare3")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u64 __spare3[8] + * } + */ + public static final long __spare3$offset() { + return __spare3$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u64 __spare3[8] + * } + */ + public static MemorySegment __spare3(MemorySegment struct) { + return struct.asSlice(__spare3$OFFSET, __spare3$LAYOUT.byteSize()); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u64 __spare3[8] + * } + */ + public static void __spare3(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, __spare3$OFFSET, __spare3$LAYOUT.byteSize()); + } + + private static long[] __spare3$DIMS = { 8 }; + + /** + * Dimensions for array field: + * {@snippet lang=c : + * __u64 __spare3[8] + * } + */ + public static long[] __spare3$dimensions() { + return __spare3$DIMS; + } + private static final VarHandle __spare3$ELEM_HANDLE = __spare3$LAYOUT.varHandle(sequenceElement()); + + /** + * Indexed getter for field: + * {@snippet lang=c : + * __u64 __spare3[8] + * } + */ + public static long __spare3(MemorySegment struct, long index0) { + return (long)__spare3$ELEM_HANDLE.get(struct, __spare3$OFFSET, index0); + } + + /** + * Indexed setter for field: + * {@snippet lang=c : + * __u64 __spare3[8] + * } + */ + public static void __spare3(MemorySegment struct, long index0, long fieldValue) { + __spare3$ELEM_HANDLE.set(struct, __spare3$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-gen/org/linux/statx_timestamp.java b/resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/statx_timestamp.java new file mode 100644 index 00000000000..b5051dbd88e --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/statx_timestamp.java @@ -0,0 +1,219 @@ +// Generated by jextract + +package org.linux; + +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 statx_timestamp { + * __s64 tv_sec; + * __u32 tv_nsec; + * __s32 __reserved; + * } + * } + */ +public class statx_timestamp { + + statx_timestamp() { + // Should not be called directly + } + + private static final GroupLayout $LAYOUT = MemoryLayout.structLayout( + LibC.C_LONG_LONG.withName("tv_sec"), + LibC.C_INT.withName("tv_nsec"), + LibC.C_INT.withName("__reserved") + ).withName("statx_timestamp"); + + /** + * The layout of this struct + */ + public static final GroupLayout layout() { + return $LAYOUT; + } + + private static final OfLong tv_sec$LAYOUT = (OfLong)$LAYOUT.select(groupElement("tv_sec")); + + /** + * Layout for field: + * {@snippet lang=c : + * __s64 tv_sec + * } + */ + public static final OfLong tv_sec$layout() { + return tv_sec$LAYOUT; + } + + private static final long tv_sec$OFFSET = $LAYOUT.byteOffset(groupElement("tv_sec")); + + /** + * Offset for field: + * {@snippet lang=c : + * __s64 tv_sec + * } + */ + public static final long tv_sec$offset() { + return tv_sec$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __s64 tv_sec + * } + */ + public static long tv_sec(MemorySegment struct) { + return struct.get(tv_sec$LAYOUT, tv_sec$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __s64 tv_sec + * } + */ + public static void tv_sec(MemorySegment struct, long fieldValue) { + struct.set(tv_sec$LAYOUT, tv_sec$OFFSET, fieldValue); + } + + private static final OfInt tv_nsec$LAYOUT = (OfInt)$LAYOUT.select(groupElement("tv_nsec")); + + /** + * Layout for field: + * {@snippet lang=c : + * __u32 tv_nsec + * } + */ + public static final OfInt tv_nsec$layout() { + return tv_nsec$LAYOUT; + } + + private static final long tv_nsec$OFFSET = $LAYOUT.byteOffset(groupElement("tv_nsec")); + + /** + * Offset for field: + * {@snippet lang=c : + * __u32 tv_nsec + * } + */ + public static final long tv_nsec$offset() { + return tv_nsec$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __u32 tv_nsec + * } + */ + public static int tv_nsec(MemorySegment struct) { + return struct.get(tv_nsec$LAYOUT, tv_nsec$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __u32 tv_nsec + * } + */ + public static void tv_nsec(MemorySegment struct, int fieldValue) { + struct.set(tv_nsec$LAYOUT, tv_nsec$OFFSET, fieldValue); + } + + private static final OfInt __reserved$LAYOUT = (OfInt)$LAYOUT.select(groupElement("__reserved")); + + /** + * Layout for field: + * {@snippet lang=c : + * __s32 __reserved + * } + */ + public static final OfInt __reserved$layout() { + return __reserved$LAYOUT; + } + + private static final long __reserved$OFFSET = $LAYOUT.byteOffset(groupElement("__reserved")); + + /** + * Offset for field: + * {@snippet lang=c : + * __s32 __reserved + * } + */ + public static final long __reserved$offset() { + return __reserved$OFFSET; + } + + /** + * Getter for field: + * {@snippet lang=c : + * __s32 __reserved + * } + */ + public static int __reserved(MemorySegment struct) { + return struct.get(__reserved$LAYOUT, __reserved$OFFSET); + } + + /** + * Setter for field: + * {@snippet lang=c : + * __s32 __reserved + * } + */ + public static void __reserved(MemorySegment struct, int fieldValue) { + struct.set(__reserved$LAYOUT, __reserved$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/org/eclipse/core/internal/filesystem/local/LocalFileNativesManager.java b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/LocalFileNativesManager.java index 8a9fe40831f..93068cfa1ad 100644 --- a/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/LocalFileNativesManager.java +++ b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/LocalFileNativesManager.java @@ -18,6 +18,7 @@ import java.util.Set; import org.eclipse.core.filesystem.IFileInfo; import org.eclipse.core.filesystem.provider.FileInfo; +import org.eclipse.core.internal.filesystem.local.ffm.LinuxFfmHandler; import org.eclipse.core.internal.filesystem.local.linux.LinuxFileHandler; import org.eclipse.core.internal.filesystem.local.linux.LinuxFileNatives; import org.eclipse.core.internal.filesystem.local.nio.DefaultHandler; @@ -37,12 +38,19 @@ *

Please notice that the native implementation is significantly faster than the non-native * one. The BenchFileStore test runs 3.1 times faster on Linux with the native code than * without it.

+ * + *

On Linux the Foreign Function & Memory API is preferred over the native libraries, + * because it needs no platform specific fragment and therefore offers bulk file info + * fetching on every architecture. Set -Declipse.filesystem.useFfm=false to fall back to + * the JNI implementations.

*/ public class LocalFileNativesManager { public static final boolean PROPERTY_USE_NATIVE_DEFAULT = true; public static final boolean PROPERTY_USE_FAST_LINUX_NATIVES_DEFAULT = true; + public static final boolean PROPERTY_USE_FFM_DEFAULT = true; public static final String PROPERTY_USE_NATIVES = "eclipse.filesystem.useNatives"; //$NON-NLS-1$ public static final String PROPERTY_USE_FAST_LINUX_NATIVES = "eclipse.filesystem.useFastLinuxNatives"; //$NON-NLS-1$ + public static final String PROPERTY_USE_FFM = "eclipse.filesystem.useFfm"; //$NON-NLS-1$ private static NativeHandler HANDLER; static { @@ -54,16 +62,32 @@ public class LocalFileNativesManager { */ public static void reset() { setUsingNative(Boolean.parseBoolean(System.getProperty(PROPERTY_USE_NATIVES, String.valueOf(PROPERTY_USE_NATIVE_DEFAULT))), - Boolean.parseBoolean(System.getProperty(PROPERTY_USE_FAST_LINUX_NATIVES, String.valueOf(PROPERTY_USE_FAST_LINUX_NATIVES_DEFAULT)))); + Boolean.parseBoolean(System.getProperty(PROPERTY_USE_FAST_LINUX_NATIVES, String.valueOf(PROPERTY_USE_FAST_LINUX_NATIVES_DEFAULT))), + Boolean.parseBoolean(System.getProperty(PROPERTY_USE_FFM, String.valueOf(PROPERTY_USE_FFM_DEFAULT)))); } /** - * Try to set the usage of natives to the provided value + * Try to set the usage of the native libraries to the provided value, without + * using the Foreign Function & Memory API. + * * @return true if natives are used as result of this call false otherwise */ public static boolean setUsingNative(boolean useNatives, boolean useFastLinuxNatives) { + return setUsingNative(useNatives, useFastLinuxNatives, false); + } + + /** + * Try to set the usage of natives to the provided value + * @return true if natives are used as result of this call false otherwise + */ + public static boolean setUsingNative(boolean useNatives, boolean useFastLinuxNatives, boolean useFfm) { boolean nativesAreUsed; - if (useNatives && useFastLinuxNatives && Platform.OS.isLinux() && Platform.ARCH_X86_64.equals(Platform.getOSArch()) && LinuxFileNatives.isUsingNatives()) { + if (useNatives && useFfm && LinuxFfmHandler.isAvailable()) { + // Bulk file info fetching without a platform specific library, so this works on + // every Linux architecture rather than only on x86_64. + HANDLER = new LinuxFfmHandler(); + nativesAreUsed = true; + } else if (useNatives && useFastLinuxNatives && Platform.OS.isLinux() && Platform.ARCH_X86_64.equals(Platform.getOSArch()) && LinuxFileNatives.isUsingNatives()) { // Linux x86_64 architecture supports faster (bulk) file info fetching. HANDLER = new LinuxFileHandler(); nativesAreUsed = true; diff --git a/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/ffm/LinuxFfmHandler.java b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/ffm/LinuxFfmHandler.java new file mode 100644 index 00000000000..ef13398f928 --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/ffm/LinuxFfmHandler.java @@ -0,0 +1,339 @@ +/******************************************************************************* + * Copyright (c) 2026 vogella GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Lars Vogel - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.filesystem.local.ffm; + +import java.io.File; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.IFileInfo; +import org.eclipse.core.filesystem.provider.FileInfo; +import org.eclipse.core.internal.filesystem.local.Convert; +import org.eclipse.core.internal.filesystem.local.NativeHandler; +import org.eclipse.core.runtime.Platform; +import org.linux.LibC; +import org.linux.dirent; +import org.linux.statx; +import org.linux.statx_timestamp; + +/** + * Reads file attributes on Linux through the Foreign Function & Memory API, + * without a platform specific native library. A directory is listed with one + * {@code opendir} and one {@code statx} per entry, so nothing is allocated per + * file beyond the resulting {@link FileInfo}. + */ +public final class LinuxFfmHandler extends NativeHandler { + + private static final int ATTRIBUTES = EFS.ATTRIBUTE_READ_ONLY | EFS.ATTRIBUTE_EXECUTABLE | EFS.ATTRIBUTE_SYMLINK + | EFS.ATTRIBUTE_LINK_TARGET | EFS.ATTRIBUTE_OWNER_READ | EFS.ATTRIBUTE_OWNER_WRITE + | EFS.ATTRIBUTE_OWNER_EXECUTE | EFS.ATTRIBUTE_GROUP_READ | EFS.ATTRIBUTE_GROUP_WRITE + | EFS.ATTRIBUTE_GROUP_EXECUTE | EFS.ATTRIBUTE_OTHER_READ | EFS.ATTRIBUTE_OTHER_WRITE + | EFS.ATTRIBUTE_OTHER_EXECUTE; + + private static final boolean USE_MILLISECOND_RESOLUTION = Boolean.parseBoolean(System.getProperty( + "eclipse.filesystem.useNatives.modificationTimestampMillisecondsResolution", "true")); //$NON-NLS-1$ //$NON-NLS-2$ + + private static final Charset PLATFORM_CHARSET = Platform.getSystemCharset(); + + private static final IFileInfo[] NO_INFOS = {}; + + /** + * Whether this handler can be used, which needs a Linux kernel offering + * {@code statx} and a libc exporting it. + */ + public static boolean isAvailable() { + return Platform.OS.isLinux() && LinuxLibC.isAvailable(); + } + + @Override + public int getSupportedAttributes() { + return ATTRIBUTES; + } + + @Override + public FileInfo fetchFileInfo(String fileName) { + Scratch scratch = Scratch.current(); + FileInfo info = fetchFileInfo(scratch, LibC.AT_FDCWD(), scratch.path(fileName), null, true); + if (info.getName().isEmpty()) { + // On a case insensitive file system the real name is not known, and finding it + // out is expensive, so the name as given is used even though its case may + // differ. + info.setName(new File(fileName).getName()); + } + return info; + } + + @Override + public String[] listDirectoryNames(String fileName) { + Scratch scratch = Scratch.current(); + MemorySegment dir = openDirectory(scratch, fileName); + if (dir == null) { + return EMPTY_STRING_ARRAY; + } + try { + List names = new ArrayList<>(); + forEachEntry(dir, (_, name) -> names.add(name)); + return names.toArray(String[]::new); + } finally { + LibC.closedir(dir); + } + } + + @Override + public IFileInfo[] listDirectoryAndGetFileInfos(String fileName) { + Scratch scratch = Scratch.current(); + MemorySegment dir = openDirectory(scratch, fileName); + if (dir == null) { + return NO_INFOS; + } + try { + int dirFd = LibC.dirfd(dir); + List infos = new ArrayList<>(); + // d_name is already a NUL terminated C string, so it is handed to statx as it + // is instead of being copied into a buffer of our own. + forEachEntry(dir, + (entry, name) -> infos.add(fetchFileInfo(scratch, dirFd, dirent.d_name(entry), name, false))); + return infos.toArray(IFileInfo[]::new); + } finally { + LibC.closedir(dir); + } + } + + @Override + public boolean putFileInfo(String fileName, IFileInfo info, int options) { + int mode = 0; + if (info.getAttribute(EFS.ATTRIBUTE_OWNER_READ)) { + mode |= LibC.S_IRUSR(); + } + if (info.getAttribute(EFS.ATTRIBUTE_OWNER_WRITE)) { + mode |= LibC.S_IWUSR(); + } + if (info.getAttribute(EFS.ATTRIBUTE_OWNER_EXECUTE)) { + mode |= LibC.S_IXUSR(); + } + if (info.getAttribute(EFS.ATTRIBUTE_GROUP_READ)) { + mode |= LibC.S_IRGRP(); + } + if (info.getAttribute(EFS.ATTRIBUTE_GROUP_WRITE)) { + mode |= LibC.S_IWGRP(); + } + if (info.getAttribute(EFS.ATTRIBUTE_GROUP_EXECUTE)) { + mode |= LibC.S_IXGRP(); + } + if (info.getAttribute(EFS.ATTRIBUTE_OTHER_READ)) { + mode |= LibC.S_IROTH(); + } + if (info.getAttribute(EFS.ATTRIBUTE_OTHER_WRITE)) { + mode |= LibC.S_IWOTH(); + } + if (info.getAttribute(EFS.ATTRIBUTE_OTHER_EXECUTE)) { + mode |= LibC.S_IXOTH(); + } + Scratch scratch = Scratch.current(); + return LibC.chmod(scratch.path(fileName), mode) == 0; + } + + /** What is passed to {@link #forEachEntry} for every entry of a directory. */ + private interface EntryVisitor { + void visit(MemorySegment entry, String name); + } + + /** + * The native buffers these calls need, held per thread and reused, because + * allocating them per call made this one of the largest allocators in the whole + * IDE during a refresh. They are freed with the thread, since the arena is + * garbage collected. + */ + private static final class Scratch { + private static final ThreadLocal CURRENT = ThreadLocal.withInitial(Scratch::new); + + private final Arena arena = Arena.ofAuto(); + final MemorySegment errno = LinuxLibC.allocateErrno(arena); + final MemorySegment stat = arena.allocate(statx.sizeof()); + private final MemorySegment path = arena.allocate(LibC.PATH_MAX() + 1); + private MemorySegment linkTarget; + + static Scratch current() { + return CURRENT.get(); + } + + /** The file name in the reusable buffer, as a NUL terminated C string. */ + MemorySegment path(String fileName) { + byte[] bytes = Convert.toPlatformBytes(fileName); + MemorySegment target = bytes.length < path.byteSize() ? path : arena.allocate(bytes.length + 1); + MemorySegment.copy(bytes, 0, target, ValueLayout.JAVA_BYTE, 0, bytes.length); + target.set(ValueLayout.JAVA_BYTE, bytes.length, (byte) 0); + return target; + } + + MemorySegment linkTarget() { + if (linkTarget == null) { + linkTarget = arena.allocate(LibC.PATH_MAX()); + } + return linkTarget; + } + } + + private static MemorySegment openDirectory(Scratch scratch, String fileName) { + MemorySegment dir = LibC.opendir(scratch.path(fileName)); + return dir.address() == 0 ? null : dir; + } + + /** + * Reads the directory to its end, passing every entry except {@code .} and + * {@code ..} to the visitor together with its name. + */ + private static void forEachEntry(MemorySegment dir, EntryVisitor visitor) { + while (true) { + MemorySegment entry = LibC.readdir(dir); + if (entry.address() == 0) { + return; + } + // readdir hands out a pointer into a buffer of unknown size, so the entry has + // to be given one before anything can be read from it. d_name is NUL terminated + // well inside the struct, so nothing past the entry itself is ever read. + MemorySegment bounded = entry.reinterpret(dirent.sizeof()); + String name = dirent.d_name(bounded).getString(0, PLATFORM_CHARSET); + if (!".".equals(name) && !"..".equals(name)) { //$NON-NLS-1$ //$NON-NLS-2$ + visitor.visit(bounded, name); + } + } + } + + /** + * Stats {@code path} relative to {@code dirFd} and converts the result, keeping + * the behaviour of the JNI handler: a symbolic link is reported with the + * attributes of its target plus the link target as a string attribute, and a + * stat that fails is converted as if it had returned an all zero struct, so a + * file that does not exist yields an info that does not exist rather than an + * error. + *

+ * {@code defaultAttributesWhenMissing} reproduces that a single file which is + * simply not there is reported with the default attributes of a fresh + * {@link FileInfo}, as every other handler does, while a directory entry that + * disappeared between listing and stating it is reported with none. + */ + private static FileInfo fetchFileInfo(Scratch scratch, int dirFd, MemorySegment path, String name, + boolean defaultAttributesWhenMissing) { + if (LinuxLibC.statx(scratch.errno, dirFd, path, false, scratch.stat) != 0) { + int errno = LinuxLibC.errno(scratch.errno); + if (defaultAttributesWhenMissing && errno == LibC.ENOENT()) { + return named(name); + } + return convert(name, errno, null, null); + } + if ((mode(scratch.stat) & LibC.S_IFMT()) != LibC.S_IFLNK()) { + return convert(name, 0, scratch.stat, null); + } + // A link is reported with the attributes of what it points at, so it is stated + // a second time, following the link this time. + int errno = 0; + MemorySegment stat = scratch.stat; + if (LinuxLibC.statx(scratch.errno, dirFd, path, true, stat) != 0) { + errno = LinuxLibC.errno(scratch.errno); + stat = null; + } + return convert(name, errno, stat, readLinkTarget(scratch, dirFd, path)); + } + + private static int mode(MemorySegment stat) { + return Short.toUnsignedInt(statx.stx_mode(stat)); + } + + private static FileInfo named(String name) { + FileInfo info = new FileInfo(); + if (name != null) { + info.setName(name); + } + return info; + } + + private static String readLinkTarget(Scratch scratch, int dirFd, MemorySegment path) { + MemorySegment buffer = scratch.linkTarget(); + long length = LibC.readlinkat(dirFd, path, buffer, buffer.byteSize()); + if (length <= 0) { + return ""; //$NON-NLS-1$ + } + byte[] target = buffer.asSlice(0, length).toArray(ValueLayout.JAVA_BYTE); + return new String(target, PLATFORM_CHARSET); + } + + /** + * Builds the info of one file. A null {@code stat} stands for a + * stat that failed and is treated as an all zero struct, and a non + * null {@code linkTarget} marks the file as a symbolic link, empty + * if the target could not be read. + */ + private static FileInfo convert(String name, int errno, MemorySegment stat, String linkTarget) { + FileInfo info = named(name); + if (linkTarget != null) { + info.setAttribute(EFS.ATTRIBUTE_SYMLINK, true); + if (!linkTarget.isEmpty()) { + info.setStringAttribute(EFS.ATTRIBUTE_LINK_TARGET, linkTarget); + } + } + if (errno != 0 && errno != LibC.ENOENT()) { + info.setError(IFileInfo.IO_ERROR); + return info; + } + info.setExists(errno != LibC.ENOENT()); + int mode = stat == null ? 0 : mode(stat); + if (stat != null) { + info.setLength(statx.stx_size(stat)); + MemorySegment modified = statx.stx_mtime(stat); + long lastModified = statx_timestamp.tv_sec(modified) * 1_000; + if (USE_MILLISECOND_RESOLUTION) { + lastModified += Integer.toUnsignedLong(statx_timestamp.tv_nsec(modified)) / 1_000_000L; + } + info.setLastModified(lastModified); + } + if ((mode & LibC.S_IFMT()) == LibC.S_IFDIR()) { + info.setDirectory(true); + } + // FileInfo starts out owner readable and writable, so those two are cleared + // rather than set. + if ((mode & LibC.S_IRUSR()) == 0) { + info.setAttribute(EFS.ATTRIBUTE_OWNER_READ, false); + } + if ((mode & LibC.S_IWUSR()) == 0) { + info.setAttribute(EFS.ATTRIBUTE_OWNER_WRITE, false); + } + if ((mode & LibC.S_IXUSR()) != 0) { + info.setAttribute(EFS.ATTRIBUTE_OWNER_EXECUTE, true); + } + if ((mode & LibC.S_IRGRP()) != 0) { + info.setAttribute(EFS.ATTRIBUTE_GROUP_READ, true); + } + if ((mode & LibC.S_IWGRP()) != 0) { + info.setAttribute(EFS.ATTRIBUTE_GROUP_WRITE, true); + } + if ((mode & LibC.S_IXGRP()) != 0) { + info.setAttribute(EFS.ATTRIBUTE_GROUP_EXECUTE, true); + } + if ((mode & LibC.S_IROTH()) != 0) { + info.setAttribute(EFS.ATTRIBUTE_OTHER_READ, true); + } + if ((mode & LibC.S_IWOTH()) != 0) { + info.setAttribute(EFS.ATTRIBUTE_OTHER_WRITE, true); + } + if ((mode & LibC.S_IXOTH()) != 0) { + info.setAttribute(EFS.ATTRIBUTE_OTHER_EXECUTE, true); + } + return info; + } +} diff --git a/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/ffm/LinuxLibC.java b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/ffm/LinuxLibC.java new file mode 100644 index 00000000000..edc9dd0879e --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/ffm/LinuxLibC.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2026 vogella GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Lars Vogel - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.filesystem.local.ffm; + +import java.lang.foreign.Arena; +import java.lang.foreign.Linker; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.VarHandle; +import java.util.NoSuchElementException; +import org.linux.LibC; + +/** + * The one call that the generated bindings cannot express: {@code statx} with + * its {@code errno}, which is what tells a file that is not there from one that + * could not be read. The descriptor and the symbol come from the generated + * bindings, only the capture option is added here. + */ +final class LinuxLibC { + + private static final Linker.Option CAPTURE_ERRNO = Linker.Option.captureCallState("errno"); //$NON-NLS-1$ + private static final MemoryLayout ERRNO_LAYOUT = Linker.Option.captureStateLayout(); + private static final VarHandle ERRNO = ERRNO_LAYOUT + .varHandle(MemoryLayout.PathElement.groupElement("errno")); //$NON-NLS-1$ + + private static final MethodHandle STATX; + private static final boolean AVAILABLE; + + static { + MethodHandle statx = null; + try { + statx = Linker.nativeLinker().downcallHandle(LibC.statx$address(), LibC.statx$descriptor(), + CAPTURE_ERRNO); + } catch (NoSuchElementException | IllegalCallerException | UnsupportedOperationException + | ExceptionInInitializerError | NoClassDefFoundError e) { + // statx needs glibc 2.28, and native access may be denied outright + } + STATX = statx; + AVAILABLE = statx != null; + } + + static boolean isAvailable() { + return AVAILABLE; + } + + static MemorySegment allocateErrno(Arena arena) { + return arena.allocate(ERRNO_LAYOUT); + } + + static int errno(MemorySegment capturedState) { + return (int) ERRNO.get(capturedState, 0L); + } + + /** + * Fills {@code buffer} with the basic statx fields of the given path and returns + * 0 on success, leaving the reason in {@code errno} otherwise. + */ + static int statx(MemorySegment errno, int dirFd, MemorySegment path, boolean followLinks, MemorySegment buffer) { + int flags = LibC.AT_STATX_SYNC_AS_STAT() | (followLinks ? 0 : LibC.AT_SYMLINK_NOFOLLOW()); + int mask = LibC.STATX_TYPE() | LibC.STATX_MODE() | LibC.STATX_SIZE() | LibC.STATX_MTIME(); + try { + return (int) STATX.invokeExact(errno, dirFd, path, flags, mask, buffer); + } catch (Throwable e) { + if (e instanceof RuntimeException runtime) { + throw runtime; + } + if (e instanceof Error error) { + throw error; + } + throw new IllegalStateException(e); + } + } + + private LinuxLibC() { + } +} diff --git a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/perf/BenchFileStore.java b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/perf/BenchFileStore.java index bc469137c09..6bd58f51c0f 100644 --- a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/perf/BenchFileStore.java +++ b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/perf/BenchFileStore.java @@ -15,6 +15,7 @@ import static org.eclipse.core.tests.harness.FileSystemHelper.getRandomLocation; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import org.eclipse.core.filesystem.EFS; @@ -113,6 +114,24 @@ public void testStoreNotExitsNative_Linux() throws Throwable { } } + @Test + public void testStoreExitsFfm_Linux() throws Throwable { + if (Platform.OS.isLinux()) { + withFfm(() -> { + new StoreTestRunner(true).run(getClass(), testInfo.getDisplayName(), REPEATS, LOOP_SIZE); + }); + } + } + + @Test + public void testStoreNotExitsFfm_Linux() throws Throwable { + if (Platform.OS.isLinux()) { + withFfm(() -> { + new StoreTestRunner(false).run(getClass(), testInfo.getDisplayName(), REPEATS, LOOP_SIZE); + }); + } + } + @Test public void testStoreExitsNio() throws Throwable { withNatives(false, false, () -> { @@ -139,4 +158,14 @@ private static void withNatives(boolean natives, boolean useFastLinuxNatives, Ex } } + private static void withFfm(Executable runnable) throws Throwable { + try { + assertTrue(LocalFileNativesManager.setUsingNative(true, false, true), + "can't use the Foreign Function & Memory API"); + runnable.execute(); + } finally { + LocalFileNativesManager.reset(); + } + } + } \ No newline at end of file