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..5f8ba757770 --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/generateLinuxH.sh @@ -0,0 +1,153 @@ +#!/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")" + +rm -rf src-gen/org/linux +jextract --output src-gen \ + --define-macro _GNU_SOURCE \ + --include-function statx \ + --include-function opendir \ + --include-function readdir \ + --include-function closedir \ + --include-function dirfd \ + --include-function readlinkat \ + --include-struct statx \ + --include-struct statx_timestamp \ + --include-struct dirent \ + --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 \ + dirent.h errno.h fcntl.h limits.h sys/stat.h unistd.h + +# jextract cannot select single struct fields or accessors, so drop every generated +# member that neither the sources in src nor another kept member refer to. +find src -name '*.java' | xargs awk -v gen="$(echo src-gen/org/linux/*.java)" ' +function code(line) { + # Drops comments and string literals, which name fields without using them. + if (line ~ /^[[:space:]]*(\/\*|\*|\/\/)/) { + return "" + } + gsub(/"[^"]*"/, "\"\"", line) + return line "\n" +} +function mentions(text, token, rest, i, before, after) { + rest = text + while ((i = index(rest, token)) > 0) { + before = i == 1 ? "" : substr(rest, i - 1, 1) + after = substr(rest, i + length(token), 1) + if (before !~ /[[:alnum:]_$.]/ && after !~ /[[:alnum:]_$]/) { + return 1 + } + rest = substr(rest, i + length(token)) + } + return 0 +} +{ sources = sources code($0) } +END { + n = split(gen, files, " ") + for (f = 1; f <= n; f++) { + cls = files[f]; sub(/.*\//, "", cls); sub(/\.java$/, "", cls) + unit = cls; sub(/\$shared$/, "", unit) + depth = 0; javadoc = 0; complete = 1; phase = "head" + while ((getline line < files[f]) > 0) { + if (phase == "head") { + head[f] = head[f] line "\n" + if (line ~ /^(public )?(final )?class /) { phase = "body" } + continue + } + if (phase == "tail" || (depth == 0 && line == "}")) { + phase = "tail"; tail[f] = tail[f] line "\n" + continue + } + if (m == 0 || complete) { + m++; mfile[m] = f; mcls[m] = cls; munit[m] = unit; complete = 0 + if (line ~ /^[[:space:]]*$/) { blank[m] = 1; complete = 1 } + } + text[m] = text[m] line "\n" + if (blank[m]) { continue } + if (line ~ /^[[:space:]]*\/\*\*/) { javadoc = 1 } + if (javadoc) { + if (line ~ /\*\//) { javadoc = 0 } + continue + } + mcode[m] = mcode[m] code(line) + if (name[m] == "") { + decl = line + if (decl ~ /class [[:alnum:]_$]+/) { + sub(/.*class /, "", decl); sub(/[^[:alnum:]_$].*/, "", decl) + } else { + sub(/[[:space:]]*[=(;].*/, "", decl); sub(/.*[[:space:]]/, "", decl) + } + name[m] = decl + public[m] = line ~ /^[[:space:]]*public / + } + opened = gsub(/\{/, "{", line); closed = gsub(/\}/, "}", line) + depth += opened - closed + if (depth == 0 && line ~ /[;}][[:space:]]*$/) { complete = 1 } + } + close(files[f]) + } + # Keeps what is reachable from the sources, repeating until nothing changes. + do { + changed = 0 + used = sources + for (i = 1; i <= m; i++) { if (keep[i]) { used = used mcode[i] } } + for (i = 1; i <= m; i++) { + if (keep[i] || blank[i]) { continue } + found = name[i] == mcls[i] || mentions(used, mcls[i] "." name[i]) || mentions(used, munit[i] "." name[i]) + for (j = 1; !found && j <= m; j++) { + if (keep[j] && munit[j] == munit[i] && mentions(mcode[j], name[i])) { found = 1 } + } + if (found) { keep[i] = 1; changed = 1 } + } + } while (changed) + for (f = 1; f <= n; f++) { + out = head[f] + for (i = 1; i <= m; i++) { + if (mfile[i] == f && (keep[i] || blank[i])) { out = out text[i] } + } + printf "%s%s", out, tail[f] > files[f] + close(files[f]) + } +}' + +# The bundle's compiler settings report unused imports and every non-NLS string, so drop +# the imports a file does not use and suppress the rest on each top-level class. +used() { + grep -vE '^[[:space:]]*(import |\*|/\*\*|//)' "$2" \ + | grep -qE "(^|[^.[:alnum:]_\$])($1)([^[:alnum:]_\$]|\$)" +} +for f in src-gen/org/linux/*.java; do + cat -s "$f" > "$f.tmp" && mv "$f.tmp" "$f" + for entry in \ + 'java.lang.invoke.*=MethodHandles?|MethodType|VarHandle' \ + 'java.nio.ByteOrder=ByteOrder' \ + 'java.util.*=Arrays|Objects|Optional|List|Map' \ + 'java.util.function.*=Consumer|Function|Supplier|BiFunction|IntFunction' \ + 'java.util.stream.*=Stream|IntStream|Collectors' \ + 'static java.lang.foreign.ValueLayout.*=JAVA_[A-Z_]+|ADDRESS(_UNALIGNED)?|Of[A-Z][a-z]+' \ + 'static java.lang.foreign.MemoryLayout.PathElement.*=groupElement|sequenceElement|dereferenceElement'; do + import=$(printf '%s' "${entry%%=*}" | sed 's/[.*]/\\&/g') + used "${entry#*=}" "$f" || sed -i "\\|^import $import;\$|d" "$f" + done + sed -i 's/^\(public \)\{0,1\}\(final \)\{0,1\}class /@SuppressWarnings("all")\n&/' "$f" +done 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..d37563901dc --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/LibC$shared.java @@ -0,0 +1,35 @@ +// Generated by jextract + +package org.linux; + +import java.lang.foreign.*; +import java.util.*; +import java.util.stream.*; + + +@SuppressWarnings("all") +public class LibC$shared { + + LibC$shared() { + // Should not be called directly + } + + 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 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); + } + +} + 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..06b01a814b6 --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/LibC.java @@ -0,0 +1,407 @@ +// Generated by jextract + +package org.linux; + +import java.lang.invoke.*; +import java.lang.foreign.*; + + +@SuppressWarnings("all") +public class LibC extends LibC$shared { + + LibC() { + // Should not be called directly + } + + 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); + } + + /** + * {@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); + } + + /** + * {@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); + } + + /** + * {@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); + } + + /** + * {@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 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; + } + + /** + * 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); + } + + /** + * {@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 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..14c99d02fef --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/dirent.java @@ -0,0 +1,96 @@ +// Generated by jextract + +package org.linux; + +import java.lang.invoke.*; +import java.lang.foreign.*; + +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]; + * } + * } + */ +@SuppressWarnings("all") +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 SequenceLayout d_name$LAYOUT = (SequenceLayout)$LAYOUT.select(groupElement("d_name")); + + private static final long d_name$OFFSET = $LAYOUT.byteOffset(groupElement("d_name")); + + /** + * 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 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); + } + + /** + * The size (in bytes) of this struct + */ + public static long sizeof() { return layout().byteSize(); } + +} + 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..e537b273f20 --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/statx.java @@ -0,0 +1,173 @@ +// Generated by jextract + +package org.linux; + +import java.lang.foreign.*; + +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]; + * } + * } + */ +@SuppressWarnings("all") +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 OfShort stx_mode$LAYOUT = (OfShort)$LAYOUT.select(groupElement("stx_mode")); + + private static final long stx_mode$OFFSET = $LAYOUT.byteOffset(groupElement("stx_mode")); + + /** + * 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 OfLong stx_size$LAYOUT = (OfLong)$LAYOUT.select(groupElement("stx_size")); + + private static final long stx_size$OFFSET = $LAYOUT.byteOffset(groupElement("stx_size")); + + /** + * 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 GroupLayout stx_mtime$LAYOUT = (GroupLayout)$LAYOUT.select(groupElement("stx_mtime")); + + private static final long stx_mtime$OFFSET = $LAYOUT.byteOffset(groupElement("stx_mtime")); + + /** + * 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()); + } + + /** + * The size (in bytes) of this struct + */ + public static long sizeof() { return layout().byteSize(); } + +} + 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..9a5d5f7f7c8 --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src-gen/org/linux/statx_timestamp.java @@ -0,0 +1,88 @@ +// Generated by jextract + +package org.linux; + +import java.lang.foreign.*; + +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; + * } + * } + */ +@SuppressWarnings("all") +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")); + + private static final long tv_sec$OFFSET = $LAYOUT.byteOffset(groupElement("tv_sec")); + + /** + * 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")); + + private static final long tv_nsec$OFFSET = $LAYOUT.byteOffset(groupElement("tv_nsec")); + + /** + * 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); + } + +} + diff --git a/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/nio/LinuxDirectoryReader.java b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/nio/LinuxDirectoryReader.java new file mode 100644 index 00000000000..8b5804186b0 --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/nio/LinuxDirectoryReader.java @@ -0,0 +1,186 @@ +/******************************************************************************* + * 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.nio; + +import java.lang.foreign.Arena; +import java.lang.foreign.Linker; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.VarHandle; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +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.runtime.Platform; +import org.linux.LibC; +import org.linux.dirent; +import org.linux.statx; +import org.linux.statx_timestamp; + +/** + * Lists a directory together with the attributes of its entries on Linux, with + * one {@code readdir} and one {@code statx} per entry through the Foreign + * Function & Memory API instead of a {@code java.nio} lookup per file. + */ +final class LinuxDirectoryReader { + + private static final IFileInfo[] NO_INFOS = {}; + + private static final Charset PLATFORM_CHARSET = Platform.getSystemCharset(); + + private static final MemoryLayout ERRNO_LAYOUT = Linker.Option.captureStateLayout(); + private static final VarHandle ERRNO = ERRNO_LAYOUT + .varHandle(MemoryLayout.PathElement.groupElement("errno")); //$NON-NLS-1$ + + // jextract emits no captureCallState, and errno is what tells a missing file from an + // unreadable one. + private static final MethodHandle STATX = Platform.OS.isLinux() ? statxWithErrno() : null; + + private LinuxDirectoryReader() { + } + + private static MethodHandle statxWithErrno() { + try { + return Linker.nativeLinker().downcallHandle(LibC.statx$address(), LibC.statx$descriptor(), + Linker.Option.captureCallState("errno")); //$NON-NLS-1$ + } catch (NoSuchElementException | IllegalCallerException | UnsupportedOperationException + | ExceptionInInitializerError | NoClassDefFoundError e) { + // statx needs glibc 2.28, and native access may be denied outright + return null; + } + } + + static boolean isAvailable() { + return STATX != null; + } + + /** + * The native buffers these calls need, held per thread and reused, so that a + * listing allocates nothing per entry beyond its {@link FileInfo}. + */ + private static final class Scratch { + private static final ThreadLocal CURRENT = ThreadLocal.withInitial(Scratch::new); + + private final Arena arena = Arena.ofAuto(); + final MemorySegment errno = arena.allocate(ERRNO_LAYOUT); + final MemorySegment stat = arena.allocate(statx.sizeof()); + final MemorySegment linkTarget = arena.allocate(LibC.PATH_MAX()); + private final MemorySegment path = arena.allocate(LibC.PATH_MAX() + 1); + + /** The file name 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; + } + } + + static IFileInfo[] listDirectoryAndGetFileInfos(String fileName) { + Scratch scratch = Scratch.CURRENT.get(); + MemorySegment dir = LibC.opendir(scratch.path(fileName)); + if (dir.address() == 0) { + return NO_INFOS; + } + try { + int dirFd = LibC.dirfd(dir); + List infos = new ArrayList<>(); + while (true) { + MemorySegment entry = LibC.readdir(dir); + if (entry.address() == 0) { + return infos.toArray(IFileInfo[]::new); + } + // d_name is NUL terminated inside the struct, so bounding the entry by its + // declared size never cuts the name short. + MemorySegment name = dirent.d_name(entry.reinterpret(dirent.sizeof())); + String nameString = name.getString(0, PLATFORM_CHARSET); + if (!".".equals(nameString) && !"..".equals(nameString)) { //$NON-NLS-1$ //$NON-NLS-2$ + infos.add(fetchFileInfo(scratch, dirFd, name, nameString)); + } + } + } finally { + LibC.closedir(dir); + } + } + + /** + * Mirrors {@link PosixHandler#fetchFileInfo(String)}: a symbolic link carries + * the attributes of its target, a missing file is no error, and any other + * failure is an I/O error. + */ + private static FileInfo fetchFileInfo(Scratch scratch, int dirFd, MemorySegment path, String name) { + FileInfo info = new FileInfo(name); + int errno = statx(scratch, dirFd, path, false); + if (errno == 0 && (mode(scratch.stat) & LibC.S_IFMT()) == LibC.S_IFLNK()) { + info.setAttribute(EFS.ATTRIBUTE_SYMLINK, true); + long length = LibC.readlinkat(dirFd, path, scratch.linkTarget, scratch.linkTarget.byteSize()); + if (length > 0) { + byte[] target = scratch.linkTarget.asSlice(0, length).toArray(ValueLayout.JAVA_BYTE); + info.setStringAttribute(EFS.ATTRIBUTE_LINK_TARGET, new String(target, PLATFORM_CHARSET)); + } + errno = statx(scratch, dirFd, path, true); + } + if (errno == LibC.ENOENT()) { + return info; + } + if (errno != 0) { + info.setError(IFileInfo.IO_ERROR); + return info; + } + MemorySegment stat = scratch.stat; + int mode = mode(stat); + MemorySegment modified = statx.stx_mtime(stat); + info.setExists(true); + info.setLastModified(statx_timestamp.tv_sec(modified) * 1_000 + + Integer.toUnsignedLong(statx_timestamp.tv_nsec(modified)) / 1_000_000); + info.setLength(statx.stx_size(stat)); + info.setDirectory((mode & LibC.S_IFMT()) == LibC.S_IFDIR()); + info.setAttribute(EFS.ATTRIBUTE_OWNER_READ, (mode & LibC.S_IRUSR()) != 0); + info.setAttribute(EFS.ATTRIBUTE_OWNER_WRITE, (mode & LibC.S_IWUSR()) != 0); + info.setAttribute(EFS.ATTRIBUTE_OWNER_EXECUTE, (mode & LibC.S_IXUSR()) != 0); + info.setAttribute(EFS.ATTRIBUTE_GROUP_READ, (mode & LibC.S_IRGRP()) != 0); + info.setAttribute(EFS.ATTRIBUTE_GROUP_WRITE, (mode & LibC.S_IWGRP()) != 0); + info.setAttribute(EFS.ATTRIBUTE_GROUP_EXECUTE, (mode & LibC.S_IXGRP()) != 0); + info.setAttribute(EFS.ATTRIBUTE_OTHER_READ, (mode & LibC.S_IROTH()) != 0); + info.setAttribute(EFS.ATTRIBUTE_OTHER_WRITE, (mode & LibC.S_IWOTH()) != 0); + info.setAttribute(EFS.ATTRIBUTE_OTHER_EXECUTE, (mode & LibC.S_IXOTH()) != 0); + return info; + } + + /** Fills {@code scratch.stat} and returns 0, or the errno of the failure. */ + private static int statx(Scratch scratch, int dirFd, MemorySegment path, boolean followLinks) { + 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(); + int result; + try { + result = (int) STATX.invokeExact(scratch.errno, dirFd, path, flags, mask, scratch.stat); + } catch (RuntimeException | Error e) { + throw e; + } catch (Throwable e) { + throw new IllegalStateException(e); + } + return result == 0 ? 0 : (int) ERRNO.get(scratch.errno, 0L); + } + + private static int mode(MemorySegment stat) { + return Short.toUnsignedInt(statx.stx_mode(stat)); + } +} diff --git a/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/nio/PosixHandler.java b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/nio/PosixHandler.java index 726f11c20b7..191ec822168 100644 --- a/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/nio/PosixHandler.java +++ b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/nio/PosixHandler.java @@ -93,6 +93,14 @@ public int getSupportedAttributes() { return ATTRIBUTES; } + @Override + public IFileInfo[] listDirectoryAndGetFileInfos(String fileName) { + if (LinuxDirectoryReader.isAvailable()) { + return LinuxDirectoryReader.listDirectoryAndGetFileInfos(fileName); + } + return super.listDirectoryAndGetFileInfos(fileName); + } + @Override public boolean putFileInfo(String fileName, IFileInfo info) { Path path = Paths.get(fileName);