diff --git a/resources/bundles/org.eclipse.core.filesystem/.classpath b/resources/bundles/org.eclipse.core.filesystem/.classpath index 81fe078c20c..408b0704049 100644 --- a/resources/bundles/org.eclipse.core.filesystem/.classpath +++ b/resources/bundles/org.eclipse.core.filesystem/.classpath @@ -1,7 +1,16 @@ - + + + + + - + + + + + + diff --git a/resources/bundles/org.eclipse.core.filesystem/.gitignore b/resources/bundles/org.eclipse.core.filesystem/.gitignore new file mode 100644 index 00000000000..72876f038fe --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/.gitignore @@ -0,0 +1 @@ +/bin25/ diff --git a/resources/bundles/org.eclipse.core.filesystem/META-INF/MANIFEST.MF b/resources/bundles/org.eclipse.core.filesystem/META-INF/MANIFEST.MF index 5bea0c13567..11afd81f7a3 100644 --- a/resources/bundles/org.eclipse.core.filesystem/META-INF/MANIFEST.MF +++ b/resources/bundles/org.eclipse.core.filesystem/META-INF/MANIFEST.MF @@ -8,11 +8,14 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)" Export-Package: org.eclipse.core.filesystem;uses:="org.eclipse.core.runtime", org.eclipse.core.filesystem.provider;uses:="org.eclipse.core.filesystem,org.eclipse.core.runtime", org.eclipse.core.internal.filesystem;x-internal:=true, + org.eclipse.core.internal.filesystem.linux;x-internal:=true, org.eclipse.core.internal.filesystem.local;x-internal:=true, org.eclipse.core.internal.filesystem.local.unix;x-internal:=true Bundle-Vendor: %providerName Bundle-RequiredExecutionEnvironment: JavaSE-17 Bundle-ActivationPolicy: lazy Automatic-Module-Name: org.eclipse.core.filesystem +Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=17))" +Multi-Release: true Import-Package: com.sun.jna;version="[5.14.0,6.0.0)", com.sun.jna.platform.win32;version="[5.14.0,6.0.0)" diff --git a/resources/bundles/org.eclipse.core.filesystem/META-INF/versions/25/OSGI-INF/MANIFEST.MF b/resources/bundles/org.eclipse.core.filesystem/META-INF/versions/25/OSGI-INF/MANIFEST.MF new file mode 100644 index 00000000000..83a03b4c8cd --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/META-INF/versions/25/OSGI-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 +Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=25))" \ No newline at end of file diff --git a/resources/bundles/org.eclipse.core.filesystem/build.properties b/resources/bundles/org.eclipse.core.filesystem/build.properties index a8d6005911d..0693c427ebd 100644 --- a/resources/bundles/org.eclipse.core.filesystem/build.properties +++ b/resources/bundles/org.eclipse.core.filesystem/build.properties @@ -13,6 +13,8 @@ ############################################################################### source.. = src/ output.. = bin/ +source.META-INF/versions/25/ = src25/ +output.META-INF/versions/25/ = bin25/ bin.includes = META-INF/,\ .,\ plugin.xml,\ diff --git a/resources/bundles/org.eclipse.core.filesystem/src25/org/eclipse/core/internal/filesystem/linux/LinuxFileFlags.java b/resources/bundles/org.eclipse.core.filesystem/src25/org/eclipse/core/internal/filesystem/linux/LinuxFileFlags.java new file mode 100644 index 00000000000..3a3fff02d3c --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src25/org/eclipse/core/internal/filesystem/linux/LinuxFileFlags.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse contributors 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: + * Eclipse contributors - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.filesystem.linux; + +/** + * Linux-specific file mode flags. + * + *

These are standard POSIX constants as defined in {@code } and are + * identical across all Linux architectures. No Mac OS X specific flags + * (such as {@code UF_IMMUTABLE} or {@code SF_IMMUTABLE}) are present here since + * they are not supported on Linux.

+ * + * @since 1.11.500 + */ +public final class LinuxFileFlags { + + /** + * Maximum number of characters in a file path including the null terminator. + */ + public static final int PATH_MAX = 4096; + + /** Bitmask for the file type bitfields in {@code st_mode}. */ + public static final int S_IFMT = 0xF000; + + /** File type: symbolic link. */ + public static final int S_IFLNK = 0xA000; + + /** File type: directory. */ + public static final int S_IFDIR = 0x4000; + + /** Owner has read permission. */ + public static final int S_IRUSR = 0x0100; + + /** Owner has write permission. */ + public static final int S_IWUSR = 0x0080; + + /** Owner has execute permission. */ + public static final int S_IXUSR = 0x0040; + + /** Group has read permission. */ + public static final int S_IRGRP = 0x0020; + + /** Group has write permission. */ + public static final int S_IWGRP = 0x0010; + + /** Group has execute permission. */ + public static final int S_IXGRP = 0x0008; + + /** Others have read permission. */ + public static final int S_IROTH = 0x0004; + + /** Others have write permission. */ + public static final int S_IWOTH = 0x0002; + + /** Others have execute permission. */ + public static final int S_IXOTH = 0x0001; + + private LinuxFileFlags() { + // not instantiable + } + +} diff --git a/resources/bundles/org.eclipse.core.filesystem/src25/org/eclipse/core/internal/filesystem/linux/LinuxFileHandler.java b/resources/bundles/org.eclipse.core.filesystem/src25/org/eclipse/core/internal/filesystem/linux/LinuxFileHandler.java new file mode 100644 index 00000000000..d42fd558ac7 --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src25/org/eclipse/core/internal/filesystem/linux/LinuxFileHandler.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse contributors 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: + * Eclipse contributors - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.filesystem.linux; + +import org.eclipse.core.filesystem.IFileInfo; +import org.eclipse.core.filesystem.provider.FileInfo; +import org.eclipse.core.internal.filesystem.local.NativeHandler; + +/** + * A {@link NativeHandler} for Linux that uses the Java 25 Foreign Function & + * Memory (FFM) API instead of JNI to access native file operations. + * + *

This handler delegates to {@link LinuxFileNatives}, which calls + * {@code statx(2)}, {@code chmod(2)} and {@code readlink(2)} directly via + * FFM downcall handles. No native shared library (.so) is required — the + * standard C library (libc) is used directly.

+ * + *

Mac OS X specific code paths present in the old {@code UnixFileHandler} + * (chflags, UF_IMMUTABLE, SF_IMMUTABLE, CoreServices unicode conversion) are + * intentionally absent. This handler is exclusively for Linux.

+ * + * @since 1.11.500 + * @see LinuxFileNatives + */ +public class LinuxFileHandler extends NativeHandler { + + @Override + public int getSupportedAttributes() { + return LinuxFileNatives.getSupportedAttributes(); + } + + @Override + public FileInfo fetchFileInfo(String fileName) { + return LinuxFileNatives.fetchFileInfo(fileName); + } + + @Override + public boolean putFileInfo(String fileName, IFileInfo info, int options) { + return LinuxFileNatives.putFileInfo(fileName, info, options); + } + +} diff --git a/resources/bundles/org.eclipse.core.filesystem/src25/org/eclipse/core/internal/filesystem/linux/LinuxFileNatives.java b/resources/bundles/org.eclipse.core.filesystem/src25/org/eclipse/core/internal/filesystem/linux/LinuxFileNatives.java new file mode 100644 index 00000000000..4151e93a01a --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src25/org/eclipse/core/internal/filesystem/linux/LinuxFileNatives.java @@ -0,0 +1,318 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse contributors 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: + * Eclipse contributors - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.filesystem.linux; + +import java.io.File; +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.StructLayout; +import java.lang.foreign.SymbolLookup; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.VarHandle; +import java.nio.charset.StandardCharsets; +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.IFileInfo; +import org.eclipse.core.filesystem.provider.FileInfo; + +/** + * Provides native file operations for Linux using the Java 25 Foreign Function & + * Memory (FFM) API instead of JNI. + * + *

This class calls the following libc functions via FFM downcall handles:

+ * + * + *

No Mac OS X specific code (chflags, UF_IMMUTABLE, SF_IMMUTABLE, tounicode, + * CoreServices) is present. File names are always encoded as UTF-8, which is the + * standard on modern Linux.

+ * + * @since 1.11.500 + */ +public final class LinuxFileNatives { + + // ---------- statx(2) flags ------------------------------------------------- + + /** {@code AT_FDCWD} — interpret pathname relative to the current working directory. */ + private static final int AT_FDCWD = -100; + + /** {@code AT_SYMLINK_NOFOLLOW} — do not follow the final symlink in pathname. */ + private static final int AT_SYMLINK_NOFOLLOW = 0x100; + + /** + * {@code STATX_BASIC_STATS} — request all basic stat fields + * ({@code stx_mode}, {@code stx_size}, {@code stx_mtime}, etc.). + */ + private static final int STATX_BASIC_STATS = 0x07FF; + + /** {@code ENOENT} errno value: no such file or directory. */ + private static final int ENOENT = 2; + + // ---------- statx struct offsets ------------------------------------------- + // + // The statx struct layout is defined in and is ABI-stable + // across all Linux architectures since Linux 4.11. The offsets below are + // the same on x86_64, aarch64, riscv64, ppc64le, s390x and every other + // Linux architecture — there is no per-arch padding variation like in the + // classic stat struct. + // + // Relevant fields used by this implementation: + // + // offset 28: __u16 stx_mode (file type + permission bits) + // offset 40: __u64 stx_size (file size in bytes) + // offset 112: __s64 stx_mtime.tv_sec (modification time, seconds) + // offset 120: __u32 stx_mtime.tv_nsec (modification time, nanoseconds) + + private static final long STATX_STRUCT_SIZE = 256L; + private static final long STATX_MODE_OFFSET = 28L; + private static final long STATX_SIZE_OFFSET = 40L; + private static final long STATX_MTIME_SEC_OFFSET = 112L; + private static final long STATX_MTIME_NSEC_OFFSET = 120L; + + // ---------- FFM handles and state ------------------------------------------ + + private static final StructLayout CAPTURED_STATE_LAYOUT; + private static final VarHandle ERRNO_VH; + private static final MethodHandle STATX_MH; + private static final MethodHandle CHMOD_MH; + private static final MethodHandle READLINK_MH; + private static final boolean AVAILABLE; + + static { + StructLayout capturedStateLayout = null; + VarHandle errnoVH = null; + MethodHandle statxMH = null; + MethodHandle chmodMH = null; + MethodHandle readlinkMH = null; + boolean available = false; + + try { + Linker linker = Linker.nativeLinker(); + SymbolLookup lookup = linker.defaultLookup(); + Linker.Option captureErrno = Linker.Option.captureCallState("errno"); //$NON-NLS-1$ + + capturedStateLayout = Linker.Option.captureStateLayout(); + errnoVH = capturedStateLayout.varHandle( + MemoryLayout.PathElement.groupElement("errno")); //$NON-NLS-1$ + + // int statx(int dirfd, const char *pathname, int flags, + // unsigned int mask, struct statx *statxbuf) + statxMH = linker.downcallHandle( + lookup.find("statx").orElseThrow(), //$NON-NLS-1$ + FunctionDescriptor.of( + ValueLayout.JAVA_INT, // return: int + ValueLayout.JAVA_INT, // dirfd + ValueLayout.ADDRESS, // pathname (const char *) + ValueLayout.JAVA_INT, // flags + ValueLayout.JAVA_INT, // mask (unsigned int) + ValueLayout.ADDRESS), // statxbuf (struct statx *) + captureErrno); + + // int chmod(const char *pathname, mode_t mode) + // mode_t is unsigned int (32-bit) on all Linux architectures + chmodMH = linker.downcallHandle( + lookup.find("chmod").orElseThrow(), //$NON-NLS-1$ + FunctionDescriptor.of( + ValueLayout.JAVA_INT, // return: int + ValueLayout.ADDRESS, // pathname (const char *) + ValueLayout.JAVA_INT), // mode (mode_t) + captureErrno); + + // ssize_t readlink(const char *pathname, char *buf, size_t bufsiz) + // ssize_t and size_t are 64-bit on all 64-bit Linux architectures + readlinkMH = linker.downcallHandle( + lookup.find("readlink").orElseThrow(), //$NON-NLS-1$ + FunctionDescriptor.of( + ValueLayout.JAVA_LONG, // return: ssize_t + ValueLayout.ADDRESS, // pathname (const char *) + ValueLayout.ADDRESS, // buf (char *) + ValueLayout.JAVA_LONG), // bufsiz (size_t) + captureErrno); + + available = true; + } catch (Exception e) { + // FFM API is not available or required libc symbols could not be found. + // isAvailable() returns false and all callers fall back gracefully. + } + + AVAILABLE = available; + CAPTURED_STATE_LAYOUT = capturedStateLayout; + ERRNO_VH = errnoVH; + STATX_MH = statxMH; + CHMOD_MH = chmodMH; + READLINK_MH = readlinkMH; + } + + // ---------- Public API ----------------------------------------------------- + + /** + * Returns {@code true} if the FFM downcall handles were initialised + * successfully and this class can be used. + * + * @return {@code true} when FFM-based native file operations are available + */ + public static boolean isAvailable() { + return AVAILABLE; + } + + /** + * Returns the bitmask of EFS file attributes supported by this implementation. + * + * @return supported EFS attribute bitmask, or {@code -1} if FFM is not available + */ + public static int getSupportedAttributes() { + if (!AVAILABLE) { + return -1; + } + // No ATTRIBUTE_IMMUTABLE: Linux does not support the BSD chflags mechanism. + return 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; + } + + /** + * Fetches file information for the given path. + * + *

Follows symlinks for the file metadata ({@code stat} semantics) but + * also reports whether the path itself is a symlink and its target + * ({@code lstat} semantics for the link itself).

+ * + * @param fileName the absolute path of the file + * @return a {@link FileInfo} populated from the file's stat data + */ + public static FileInfo fetchFileInfo(String fileName) { + FileInfo info = null; + + try (Arena arena = Arena.ofConfined()) { + MemorySegment capturedState = arena.allocate(CAPTURED_STATE_LAYOUT); + MemorySegment nameSeg = arena.allocateFrom(fileName, StandardCharsets.UTF_8); + MemorySegment statxBuf = arena.allocate(STATX_STRUCT_SIZE, 8L); + + // First call: lstat semantics (AT_SYMLINK_NOFOLLOW) to detect symlinks + int result = (int) STATX_MH.invoke(capturedState, AT_FDCWD, nameSeg, + AT_SYMLINK_NOFOLLOW, STATX_BASIC_STATS, statxBuf); + + if (result == 0) { + int mode = Short.toUnsignedInt(statxBuf.get(ValueLayout.JAVA_SHORT, STATX_MODE_OFFSET)); + + if ((mode & LinuxFileFlags.S_IFMT) == LinuxFileFlags.S_IFLNK) { + // The path is a symlink: follow it to get the target's metadata + MemorySegment targetBuf = arena.allocate(STATX_STRUCT_SIZE, 8L); + int followResult = (int) STATX_MH.invoke(capturedState, AT_FDCWD, nameSeg, + 0 /* follow symlinks */, STATX_BASIC_STATS, targetBuf); + + if (followResult == 0) { + info = statxBufToFileInfo(targetBuf); + } else { + // Broken symlink (target does not exist) + info = new FileInfo(); + int errno = (int) ERRNO_VH.get(capturedState, 0L); + if (errno != ENOENT) { + info.setError(IFileInfo.IO_ERROR); + } + } + info.setAttribute(EFS.ATTRIBUTE_SYMLINK, true); + + // Read the symlink target string + MemorySegment linkBuf = arena.allocate(LinuxFileFlags.PATH_MAX); + long len = (long) READLINK_MH.invoke(capturedState, nameSeg, linkBuf, + (long) LinuxFileFlags.PATH_MAX); + if (len > 0) { + byte[] linkBytes = linkBuf.asSlice(0L, len).toArray(ValueLayout.JAVA_BYTE); + info.setStringAttribute(EFS.ATTRIBUTE_LINK_TARGET, + new String(linkBytes, StandardCharsets.UTF_8)); + } + } else { + info = statxBufToFileInfo(statxBuf); + } + } else { + info = new FileInfo(); + int errno = (int) ERRNO_VH.get(capturedState, 0L); + if (errno != ENOENT) { + info.setError(IFileInfo.IO_ERROR); + } + } + } catch (Throwable e) { + info = new FileInfo(); + info.setError(IFileInfo.IO_ERROR); + } + + if (info.getName() == null) { + // Use the basename of the supplied path as the file name + info.setName(new File(fileName).getName()); + } + return info; + } + + /** + * Updates the file permissions for the given path. + * + * @param fileName the absolute path of the file + * @param info the desired file attributes + * @param options (unused; reserved for future use) + * @return {@code true} if the chmod call succeeded + */ + public static boolean putFileInfo(String fileName, IFileInfo info, int options) { + try (Arena arena = Arena.ofConfined()) { + MemorySegment capturedState = arena.allocate(CAPTURED_STATE_LAYOUT); + MemorySegment nameSeg = arena.allocateFrom(fileName, StandardCharsets.UTF_8); + + int mode = 0; + if (info.getAttribute(EFS.ATTRIBUTE_OWNER_READ)) mode |= LinuxFileFlags.S_IRUSR; + if (info.getAttribute(EFS.ATTRIBUTE_OWNER_WRITE)) mode |= LinuxFileFlags.S_IWUSR; + if (info.getAttribute(EFS.ATTRIBUTE_OWNER_EXECUTE)) mode |= LinuxFileFlags.S_IXUSR; + if (info.getAttribute(EFS.ATTRIBUTE_GROUP_READ)) mode |= LinuxFileFlags.S_IRGRP; + if (info.getAttribute(EFS.ATTRIBUTE_GROUP_WRITE)) mode |= LinuxFileFlags.S_IWGRP; + if (info.getAttribute(EFS.ATTRIBUTE_GROUP_EXECUTE)) mode |= LinuxFileFlags.S_IXGRP; + if (info.getAttribute(EFS.ATTRIBUTE_OTHER_READ)) mode |= LinuxFileFlags.S_IROTH; + if (info.getAttribute(EFS.ATTRIBUTE_OTHER_WRITE)) mode |= LinuxFileFlags.S_IWOTH; + if (info.getAttribute(EFS.ATTRIBUTE_OTHER_EXECUTE)) mode |= LinuxFileFlags.S_IXOTH; + + int code = (int) CHMOD_MH.invoke(capturedState, nameSeg, mode); + return code == 0; + } catch (Throwable e) { + return false; + } + } + + // ---------- Private helpers ------------------------------------------------ + + /** + * Reads the mode, size and mtime fields from a {@code struct statx} memory + * segment and converts them into a {@link FileInfo}. + */ + private static FileInfo statxBufToFileInfo(MemorySegment statxBuf) { + int mode = Short.toUnsignedInt(statxBuf.get(ValueLayout.JAVA_SHORT, STATX_MODE_OFFSET)); + long size = statxBuf.get(ValueLayout.JAVA_LONG, STATX_SIZE_OFFSET); + long mtimeSec = statxBuf.get(ValueLayout.JAVA_LONG, STATX_MTIME_SEC_OFFSET); + long mtimeNsec = Integer.toUnsignedLong(statxBuf.get(ValueLayout.JAVA_INT, STATX_MTIME_NSEC_OFFSET)); + return new LinuxStructStat(mode, size, mtimeSec, mtimeNsec).toFileInfo(); + } + + private LinuxFileNatives() { + // not instantiable + } + +} diff --git a/resources/bundles/org.eclipse.core.filesystem/src25/org/eclipse/core/internal/filesystem/linux/LinuxStructStat.java b/resources/bundles/org.eclipse.core.filesystem/src25/org/eclipse/core/internal/filesystem/linux/LinuxStructStat.java new file mode 100644 index 00000000000..258f9ed5a4e --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src25/org/eclipse/core/internal/filesystem/linux/LinuxStructStat.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse contributors 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: + * Eclipse contributors - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.filesystem.linux; + +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.provider.FileInfo; + +/** + * Holds the relevant fields from the Linux {@code struct stat} obtained via + * {@code statx(2)} and converts them to an Eclipse {@link FileInfo}. + * + *

Unlike the shared {@code StructStat} used by the JNI/Unix implementation, + * this class contains no Mac OS X specific fields (no {@code st_flags}).

+ * + * @since 1.11.500 + */ +public class LinuxStructStat { + + private static final boolean USE_MILLISECOND_RESOLUTION = Boolean.parseBoolean( + System.getProperty("eclipse.filesystem.useNatives.modificationTimestampMillisecondsResolution", //$NON-NLS-1$ + "true")); //$NON-NLS-1$ + + /** File mode (type and permission bits). */ + public final int st_mode; + + /** File size in bytes. */ + public final long st_size; + + /** Last modification time in whole seconds (since the Unix epoch). */ + public final long st_mtime; + + /** Nanosecond component of the last modification time. */ + public final long st_mtime_nsec; + + /** + * Creates a new {@code LinuxStructStat} with the given stat field values. + * + * @param st_mode file mode (type + permissions) + * @param st_size file size in bytes + * @param st_mtime modification time, whole seconds + * @param st_mtime_nsec modification time, nanosecond component + */ + public LinuxStructStat(int st_mode, long st_size, long st_mtime, long st_mtime_nsec) { + this.st_mode = st_mode; + this.st_size = st_size; + this.st_mtime = st_mtime; + this.st_mtime_nsec = st_mtime_nsec; + } + + /** + * Converts the stat data into an Eclipse {@link FileInfo}. + * + * @return a {@code FileInfo} populated from the stat fields + */ + public FileInfo toFileInfo() { + FileInfo info = new FileInfo(); + info.setExists(true); + info.setLength(st_size); + + long lastModified = st_mtime * 1_000L; + if (USE_MILLISECOND_RESOLUTION) { + lastModified += st_mtime_nsec / 1_000_000L; + } + info.setLastModified(lastModified); + + if ((st_mode & LinuxFileFlags.S_IFMT) == LinuxFileFlags.S_IFDIR) { + info.setDirectory(true); + } + + // Owner permissions (OWNER_READ and OWNER_WRITE default to true in FileInfo, + // so we only need to explicitly set them to false when the bits are absent) + if ((st_mode & LinuxFileFlags.S_IRUSR) == 0) { + info.setAttribute(EFS.ATTRIBUTE_OWNER_READ, false); + } + if ((st_mode & LinuxFileFlags.S_IWUSR) == 0) { + info.setAttribute(EFS.ATTRIBUTE_OWNER_WRITE, false); + } + if ((st_mode & LinuxFileFlags.S_IXUSR) != 0) { + info.setAttribute(EFS.ATTRIBUTE_OWNER_EXECUTE, true); + } + + // Group permissions + if ((st_mode & LinuxFileFlags.S_IRGRP) != 0) { + info.setAttribute(EFS.ATTRIBUTE_GROUP_READ, true); + } + if ((st_mode & LinuxFileFlags.S_IWGRP) != 0) { + info.setAttribute(EFS.ATTRIBUTE_GROUP_WRITE, true); + } + if ((st_mode & LinuxFileFlags.S_IXGRP) != 0) { + info.setAttribute(EFS.ATTRIBUTE_GROUP_EXECUTE, true); + } + + // Others permissions + if ((st_mode & LinuxFileFlags.S_IROTH) != 0) { + info.setAttribute(EFS.ATTRIBUTE_OTHER_READ, true); + } + if ((st_mode & LinuxFileFlags.S_IWOTH) != 0) { + info.setAttribute(EFS.ATTRIBUTE_OTHER_WRITE, true); + } + if ((st_mode & LinuxFileFlags.S_IXOTH) != 0) { + info.setAttribute(EFS.ATTRIBUTE_OTHER_EXECUTE, true); + } + + return info; + } + +} diff --git a/resources/bundles/org.eclipse.core.filesystem/src25/org/eclipse/core/internal/filesystem/local/LocalFileNativesManager.java b/resources/bundles/org.eclipse.core.filesystem/src25/org/eclipse/core/internal/filesystem/local/LocalFileNativesManager.java new file mode 100644 index 00000000000..9f22dd80e6c --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src25/org/eclipse/core/internal/filesystem/local/LocalFileNativesManager.java @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse contributors 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: + * Eclipse contributors - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.filesystem.local; + +import java.nio.file.FileSystems; +import java.util.Set; +import org.eclipse.core.filesystem.IFileInfo; +import org.eclipse.core.filesystem.provider.FileInfo; +import org.eclipse.core.internal.filesystem.linux.LinuxFileHandler; +import org.eclipse.core.internal.filesystem.linux.LinuxFileNatives; +import org.eclipse.core.internal.filesystem.local.nio.DefaultHandler; +import org.eclipse.core.internal.filesystem.local.nio.PosixHandler; +import org.eclipse.core.internal.filesystem.local.unix.UnixFileHandler; +import org.eclipse.core.internal.filesystem.local.unix.UnixFileNatives; +import org.eclipse.core.runtime.Platform; + +/** + * Java 25+ override of {@code LocalFileNativesManager}. + * + *

When running on Java 25 or later on Linux, this class prefers the + * {@link LinuxFileHandler} backed by the Java FFM API over the legacy + * JNI-based {@code UnixFileHandler}. The FFM implementation calls + * {@code statx(2)}, {@code chmod(2)} and {@code readlink(2)} directly via + * downcall handles without requiring a separately shipped native shared + * library.

+ * + *

On all other platforms (macOS, Windows) and when the FFM initialisation + * fails, the selection logic falls back to the same order as the Java 17 + * base-version of this class:

+ *
    + *
  1. JNI {@code UnixFileHandler} (non-Windows, when native library is present)
  2. + *
  3. POSIX NIO/2 {@code PosixHandler}
  4. + *
  5. DOS NIO/2 {@code Win32Handler}
  6. + *
  7. {@code DefaultHandler}
  8. + *
+ * + *

This class is placed in {@code META-INF/versions/25/} of the multi-release + * JAR and is selected automatically by the JVM when the runtime is Java 25+. + * The Java 17 version in the root of the JAR is used on older runtimes.

+ * + * @since 1.11.500 + */ +public class LocalFileNativesManager { + + /** System property that can be used to disable native file operations. */ + public static final String PROPERTY_USE_NATIVES = "eclipse.filesystem.useNatives"; //$NON-NLS-1$ + + /** Default value for {@link #PROPERTY_USE_NATIVES}. */ + public static final boolean PROPERTY_USE_NATIVE_DEFAULT = true; + + private static NativeHandler HANDLER; + + static { + reset(); + } + + /** + * Resets the handler selection to the system default determined by the + * {@value #PROPERTY_USE_NATIVES} system property. + */ + public static void reset() { + setUsingNative(Boolean.parseBoolean( + System.getProperty(PROPERTY_USE_NATIVES, String.valueOf(PROPERTY_USE_NATIVE_DEFAULT)))); + } + + /** + * Attempts to configure the native handler according to the {@code useNatives} + * flag. + * + *

On Java 25+ running on Linux the FFM-based {@link LinuxFileHandler} is + * preferred over the legacy JNI handler. On all other configurations the + * selection order matches the Java 17 base-version of this class.

+ * + * @param useNatives {@code true} to try to use a native (FFM or JNI) handler + * @return {@code true} if a native handler is active after this call + */ + public static boolean setUsingNative(boolean useNatives) { + boolean nativesAreUsed; + + if (useNatives && Platform.OS.isLinux() && LinuxFileNatives.isAvailable()) { + // Java 25+ on Linux: use the FFM-based handler. + // No native .so library is needed — libc is accessed directly. + HANDLER = new LinuxFileHandler(); + nativesAreUsed = true; + } else if (useNatives && !Platform.OS.isWindows() && UnixFileNatives.isUsingNatives()) { + // Non-Linux Unix (macOS) or Linux on older Java: fall back to JNI handler. + HANDLER = new UnixFileHandler(); + nativesAreUsed = true; + } else { + nativesAreUsed = false; + Set views = FileSystems.getDefault().supportedFileAttributeViews(); + if (views.contains("posix")) { //$NON-NLS-1$ + HANDLER = new PosixHandler(); + } else if (views.contains("dos")) { //$NON-NLS-1$ + HANDLER = new Win32Handler(); + } else { + HANDLER = new DefaultHandler(); + } + } + return nativesAreUsed; + } + + /** + * Returns the bitmask of EFS attributes supported by the active handler. + * + * @return EFS attribute bitmask + */ + public static int getSupportedAttributes() { + return HANDLER.getSupportedAttributes(); + } + + /** + * Fetches file information for the given file path. + * + * @param fileName absolute path of the file + * @return {@link FileInfo} populated with the file's attributes + */ + public static FileInfo fetchFileInfo(String fileName) { + return HANDLER.fetchFileInfo(fileName); + } + + /** + * Writes file information (permissions) for the given file path. + * + * @param fileName absolute path of the file + * @param info the desired file attributes + * @param options option flags (currently unused) + * @return {@code true} if the operation succeeded + */ + public static boolean putFileInfo(String fileName, IFileInfo info, int options) { + return HANDLER.putFileInfo(fileName, info, options); + } + +}