From c04d66895b79facea944b188044f13301df6cf0e Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 9 Sep 2025 17:05:36 +0200 Subject: [PATCH 01/16] Introduce capable_ext/want_ext fields --- .../cryptomator/jfuse/api/FuseConnInfo.java | 61 +++++++++++++++++++ .../jfuse/linux/aarch64/FuseConnInfoImpl.java | 33 +++++++++- .../jfuse/linux/aarch64/FuseFunctions.java | 30 +++++++++ .../jfuse/linux/amd64/FuseConnInfoImpl.java | 33 +++++++++- .../jfuse/linux/amd64/FuseFunctions.java | 29 +++++++++ 5 files changed, 184 insertions(+), 2 deletions(-) diff --git a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseConnInfo.java b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseConnInfo.java index e9b2718f..9c0368c6 100644 --- a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseConnInfo.java +++ b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseConnInfo.java @@ -323,6 +323,7 @@ public interface FuseConnInfo { * Capability flags that the kernel supports (read-only) * * @return {@code capable} value + * @apiNote Since libFUSE 3.17, this field is deprecated left over for ABI compatibility, use capable_ext */ int capable(); @@ -333,6 +334,8 @@ public interface FuseConnInfo { * reasonable default values before calling the init() handler. * * @return {@code want} value + * @apiNote Since libFUSE 3.17, this field is deprecated left over for ABI compatibility. + * Use want_ext with the helper functions fuse_set_feature_flag() / fuse_unset_feature_flag() */ int want(); @@ -340,6 +343,8 @@ public interface FuseConnInfo { * Sets the {@link #want()} value. * * @param wanted {@code want} value + * @apiNote Since libFUSE 3.17, this field is deprecated left over for ABI compatibility. + * Use want_ext with the helper functions fuse_set_feature_flag() / fuse_unset_feature_flag() */ void setWant(int wanted); @@ -499,4 +504,60 @@ default int asyncRead() { default void setAsyncRead(int asyncRead) { //no-op } + + /** + * Extended capability flags that the kernel supports (read-only) + * This field provides full 64-bit capability support. + * + * @since libFUSE 3.17 + */ + default long capableExt() { + return 0; + } + + /** + * Extended capability flags that the filesystem wants to enable. + * This field provides full 64-bit capability support. + *

+ * Don't set this field directly, but use the helper functions + * fuse_set_feature_flag() / fuse_unset_feature_flag() + * + * @see #setFeatureFlag(long) + * @see #unsetFeatureFlag(long) + * @since libFUSE 3.17 + */ + default long wantExt() { + return 0; + } + + /** + * Sets the {@link #wantExt()} value. + * + * @param wantExt {@code want_ext} value + * @since libFUSE 3.17 + */ + default void setWantExt(long wantExt) { + //no-op + } + + /** + * Set a feature flag in the want_ext field of fuse_conn_info. + * + * @param flag feature flag to be set + * @return true if the flag was set, false if the flag is not supported or the method is not implemented + * @since libFUSE 3.17 + */ + default boolean setFeatureFlag(long flag) { + return false; + } + + /** + * Unset a feature flag in the want_ext field of fuse_conn_info. Does nothing if the method is not implemented + * + * @param flag feature flag to be unset + * @since libFUSE 3.17 + */ + default void unsetFeatureFlag(long flag) { + //no-op + } } diff --git a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl.java b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl.java index 8b20f86d..0625739d 100644 --- a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl.java +++ b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl.java @@ -3,7 +3,6 @@ import org.cryptomator.jfuse.api.FuseConnInfo; import org.cryptomator.jfuse.linux.aarch64.extr.fuse3.fuse_conn_info; -import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; record FuseConnInfoImpl(MemorySegment segment) implements FuseConnInfo { @@ -93,4 +92,36 @@ public void setTimeGran(int timeGran) { fuse_conn_info.time_gran(segment, timeGran); } + @Override + public long capableExt() { + return fuse_conn_info.capable_ext(segment); + } + + @Override + public long wantExt() { + return fuse_conn_info.want_ext(segment); + } + + @Override + public void setWantExt(long wantExt) { + fuse_conn_info.want_ext(segment, wantExt); + } + + @Override + public boolean setFeatureFlag(long flag) { + try { + return FuseFunctions.fuse_set_feature_flag(segment, flag); + } catch (UnsupportedOperationException e) { + return false; + } + } + + @Override + public void unsetFeatureFlag(long flag) { + try { + FuseFunctions.fuse_unset_feature_flag(segment, flag); + } catch (UnsupportedOperationException e) { + //no-op + } + } } diff --git a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseFunctions.java b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseFunctions.java index 52cada82..c0439ff3 100644 --- a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseFunctions.java +++ b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseFunctions.java @@ -5,9 +5,11 @@ import java.lang.foreign.MemorySegment; import java.lang.foreign.SymbolLookup; import java.lang.invoke.MethodHandle; +import java.util.Optional; import static java.lang.foreign.ValueLayout.ADDRESS; import static java.lang.foreign.ValueLayout.JAVA_INT; +import static java.lang.foreign.ValueLayout.JAVA_LONG; /** * These method references can not be jextract'ed, partly due to jextract not being able to understand {@code #define}, @@ -18,8 +20,13 @@ class FuseFunctions { // see https://github.com/libfuse/libfuse/blob/fuse-3.12.0/include/fuse_lowlevel.h#L1892-L1923 private static final FunctionDescriptor FUSE_PARSE_CMDLINE = FunctionDescriptor.of(JAVA_INT, ADDRESS, ADDRESS); + //https://github.com/libfuse/libfuse/blob/fuse-3.17.4/lib/fuse_lowlevel.c#L2035 + private static final FunctionDescriptor FUSE_SET_FEATURE_FLAG = FunctionDescriptor.of(ADDRESS, JAVA_LONG); + private static final FunctionDescriptor FUSE_UNSET_FEATURE_FLAG = FunctionDescriptor.of(ADDRESS, JAVA_LONG); private final MethodHandle fuse_parse_cmdline; + private final Optional fuse_set_feature_flag; + private final Optional fuse_unset_feature_flag; private FuseFunctions() { var lookup = SymbolLookup.loaderLookup(); @@ -27,6 +34,10 @@ private FuseFunctions() { this.fuse_parse_cmdline = lookup.find("fuse_parse_cmdline") .map(symbol -> linker.downcallHandle(symbol, FUSE_PARSE_CMDLINE)) .orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol fuse_parse_cmdline")); + this.fuse_set_feature_flag = lookup.find("fuse_set_feature_flag") + .map(symbol -> linker.downcallHandle(symbol, FUSE_SET_FEATURE_FLAG)); + this.fuse_unset_feature_flag = lookup.find("fuse_unset_feature_flag") + .map(symbol -> linker.downcallHandle(symbol, FUSE_UNSET_FEATURE_FLAG)); } private static class Holder { @@ -41,4 +52,23 @@ public static int fuse_parse_cmdline(MemorySegment args, MemorySegment opts) { } } + public static boolean fuse_set_feature_flag(MemorySegment fuse_conn_info, long flag) throws UnsupportedOperationException { + try { + var method = Holder.INSTANCE.fuse_set_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not support fuse_set_feature_flag")); + return ((int) method.invokeExact(fuse_conn_info, flag)) != 0; + } catch (Throwable e) { + throw new AssertionError("should not reach here", e); + } + } + + public static void fuse_unset_feature_flag(MemorySegment fuse_conn_info, long flag) throws UnsupportedOperationException { + try { + var method = Holder.INSTANCE.fuse_unset_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not support fuse_unset_feature_flag")); + method.invokeExact(fuse_conn_info, flag); + } catch (Throwable e) { + throw new AssertionError("should not reach here", e); + } + } + + } diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl.java index 487e0c44..f4a32868 100644 --- a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl.java +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl.java @@ -3,7 +3,6 @@ import org.cryptomator.jfuse.api.FuseConnInfo; import org.cryptomator.jfuse.linux.amd64.extr.fuse3.fuse_conn_info; -import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; record FuseConnInfoImpl(MemorySegment segment) implements FuseConnInfo { @@ -93,4 +92,36 @@ public void setTimeGran(int timeGran) { fuse_conn_info.time_gran(segment, timeGran); } + @Override + public long capableExt() { + return fuse_conn_info.capable_ext(segment); + } + + @Override + public long wantExt() { + return fuse_conn_info.want_ext(segment); + } + + @Override + public void setWantExt(long wantExt) { + fuse_conn_info.want_ext(segment, wantExt); + } + + @Override + public boolean setFeatureFlag(long flag) { + try { + return FuseFunctions.fuse_set_feature_flag(segment, flag); + } catch (UnsupportedOperationException e) { + return false; + } + } + + @Override + public void unsetFeatureFlag(long flag) { + try { + FuseFunctions.fuse_unset_feature_flag(segment, flag); + } catch (UnsupportedOperationException e) { + //no-op + } + } } diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java index 4a182345..817f2a06 100644 --- a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java @@ -5,9 +5,11 @@ import java.lang.foreign.MemorySegment; import java.lang.foreign.SymbolLookup; import java.lang.invoke.MethodHandle; +import java.util.Optional; import static java.lang.foreign.ValueLayout.ADDRESS; import static java.lang.foreign.ValueLayout.JAVA_INT; +import static java.lang.foreign.ValueLayout.JAVA_LONG; /** * These method references can not be jextract'ed, partly due to jextract not being able to understand {@code #define}, @@ -18,8 +20,13 @@ class FuseFunctions { // see https://github.com/libfuse/libfuse/blob/fuse-3.12.0/include/fuse_lowlevel.h#L1892-L1923 private static final FunctionDescriptor FUSE_PARSE_CMDLINE = FunctionDescriptor.of(JAVA_INT, ADDRESS, ADDRESS); + //https://github.com/libfuse/libfuse/blob/fuse-3.17.4/lib/fuse_lowlevel.c#L2035 + private static final FunctionDescriptor FUSE_SET_FEATURE_FLAG = FunctionDescriptor.of(ADDRESS, JAVA_LONG); + private static final FunctionDescriptor FUSE_UNSET_FEATURE_FLAG = FunctionDescriptor.of(ADDRESS, JAVA_LONG); private final MethodHandle fuse_parse_cmdline; + private final Optional fuse_set_feature_flag; + private final Optional fuse_unset_feature_flag; private FuseFunctions() { var lookup = SymbolLookup.loaderLookup(); @@ -27,6 +34,10 @@ private FuseFunctions() { this.fuse_parse_cmdline = lookup.find("fuse_parse_cmdline") .map(symbol -> linker.downcallHandle(symbol, FUSE_PARSE_CMDLINE)) .orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol fuse_parse_cmdline")); + this.fuse_set_feature_flag = lookup.find("fuse_set_feature_flag") + .map(symbol -> linker.downcallHandle(symbol, FUSE_SET_FEATURE_FLAG)); + this.fuse_unset_feature_flag = lookup.find("fuse_unset_feature_flag") + .map(symbol -> linker.downcallHandle(symbol, FUSE_UNSET_FEATURE_FLAG)); } private static class Holder { @@ -41,4 +52,22 @@ public static int fuse_parse_cmdline(MemorySegment args, MemorySegment opts) { } } + public static boolean fuse_set_feature_flag(MemorySegment fuse_conn_info, long flag) throws UnsupportedOperationException { + try { + var method = Holder.INSTANCE.fuse_set_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not support fuse_set_feature_flag")); + return ((int) method.invokeExact(fuse_conn_info, flag)) != 0; + } catch (Throwable e) { + throw new AssertionError("should not reach here", e); + } + } + + public static void fuse_unset_feature_flag(MemorySegment fuse_conn_info, long flag) throws UnsupportedOperationException { + try { + var method = Holder.INSTANCE.fuse_unset_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not support fuse_unset_feature_flag")); + method.invokeExact(fuse_conn_info, flag); + } catch (Throwable e) { + throw new AssertionError("should not reach here", e); + } + } + } From fad20d73c7f63bae1517a391d37ef7638a5cb596 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 10 Sep 2025 10:58:31 +0200 Subject: [PATCH 02/16] add "custom" javadoc tag to maven config --- pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index 681d508d..cc0f86a7 100644 --- a/pom.xml +++ b/pom.xml @@ -119,6 +119,16 @@ org.apache.maven.plugins maven-javadoc-plugin 3.11.2 + + + + apiNote + a + API Note: + + + + org.apache.maven.plugins From 2f057fef9ba2dc9f40c04898f601ff9e97a2518d Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 10 Sep 2025 14:01:48 +0200 Subject: [PATCH 03/16] improve API docs --- .../cryptomator/jfuse/api/FuseConnInfo.java | 19 ++++++++++++++++--- .../jfuse/linux/aarch64/FuseConnInfoImpl.java | 16 ++++------------ .../jfuse/linux/amd64/FuseConnInfoImpl.java | 16 ++++------------ 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseConnInfo.java b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseConnInfo.java index 9c0368c6..63453e44 100644 --- a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseConnInfo.java +++ b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseConnInfo.java @@ -508,7 +508,10 @@ default void setAsyncRead(int asyncRead) { /** * Extended capability flags that the kernel supports (read-only) * This field provides full 64-bit capability support. + *

+ * If the version of the loaded FUSE library is below 3.17, this method does nothing. * + * @implSpec The default implementation always returns 0. * @since libFUSE 3.17 */ default long capableExt() { @@ -521,7 +524,10 @@ default long capableExt() { *

* Don't set this field directly, but use the helper functions * fuse_set_feature_flag() / fuse_unset_feature_flag() + *

+ * If the version of the loaded FUSE library is below 3.17, this method does nothing. * + * @implSpec The default implementation always returns 0. * @see #setFeatureFlag(long) * @see #unsetFeatureFlag(long) * @since libFUSE 3.17 @@ -532,8 +538,11 @@ default long wantExt() { /** * Sets the {@link #wantExt()} value. + *

+ * If the version of the loaded FUSE library is below 3.17, this method does nothing. * * @param wantExt {@code want_ext} value + * @implSpec The default implementation is a no-op. * @since libFUSE 3.17 */ default void setWantExt(long wantExt) { @@ -544,10 +553,12 @@ default void setWantExt(long wantExt) { * Set a feature flag in the want_ext field of fuse_conn_info. * * @param flag feature flag to be set - * @return true if the flag was set, false if the flag is not supported or the method is not implemented + * @return {@code true} if the flag was set, {@code false} if the flag is not supported + * @throws UnsupportedOperationException if the loaded fuse library does not implement the method + * @implSpec The default implementation discards the input and simply returns false. * @since libFUSE 3.17 */ - default boolean setFeatureFlag(long flag) { + default boolean setFeatureFlag(long flag) throws UnsupportedOperationException { return false; } @@ -555,9 +566,11 @@ default boolean setFeatureFlag(long flag) { * Unset a feature flag in the want_ext field of fuse_conn_info. Does nothing if the method is not implemented * * @param flag feature flag to be unset + * @throws UnsupportedOperationException if the loaded fuse library does not implement the method + * @implSpec The default implementation is no-op. * @since libFUSE 3.17 */ - default void unsetFeatureFlag(long flag) { + default void unsetFeatureFlag(long flag) throws UnsupportedOperationException { //no-op } } diff --git a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl.java b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl.java index 0625739d..20ad18a5 100644 --- a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl.java +++ b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl.java @@ -108,20 +108,12 @@ public void setWantExt(long wantExt) { } @Override - public boolean setFeatureFlag(long flag) { - try { - return FuseFunctions.fuse_set_feature_flag(segment, flag); - } catch (UnsupportedOperationException e) { - return false; - } + public boolean setFeatureFlag(long flag) throws UnsupportedOperationException { + return FuseFunctions.fuse_set_feature_flag(segment, flag); } @Override - public void unsetFeatureFlag(long flag) { - try { - FuseFunctions.fuse_unset_feature_flag(segment, flag); - } catch (UnsupportedOperationException e) { - //no-op - } + public void unsetFeatureFlag(long flag) throws UnsupportedOperationException { + FuseFunctions.fuse_unset_feature_flag(segment, flag); } } diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl.java index f4a32868..14e7a20e 100644 --- a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl.java +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl.java @@ -108,20 +108,12 @@ public void setWantExt(long wantExt) { } @Override - public boolean setFeatureFlag(long flag) { - try { - return FuseFunctions.fuse_set_feature_flag(segment, flag); - } catch (UnsupportedOperationException e) { - return false; - } + public boolean setFeatureFlag(long flag) throws UnsupportedOperationException { + return FuseFunctions.fuse_set_feature_flag(segment, flag); } @Override - public void unsetFeatureFlag(long flag) { - try { - FuseFunctions.fuse_unset_feature_flag(segment, flag); - } catch (UnsupportedOperationException e) { - //no-op - } + public void unsetFeatureFlag(long flag) throws UnsupportedOperationException { + FuseFunctions.fuse_unset_feature_flag(segment, flag); } } From 545d432367e558efaff2e7ce4f79870a2a7b055a Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 10 Sep 2025 14:03:03 +0200 Subject: [PATCH 04/16] extend test --- .../linux/aarch64/FuseConnInfoImplTest.java | 169 +++++++++++++----- .../linux/amd64/FuseConnInfoImplTest.java | 169 +++++++++++++----- 2 files changed, 246 insertions(+), 92 deletions(-) diff --git a/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImplTest.java b/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImplTest.java index e0733c39..6aa9eb4a 100644 --- a/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImplTest.java +++ b/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImplTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -17,67 +18,143 @@ public class FuseConnInfoImplTest { - @DisplayName("test getters") - @ParameterizedTest(name = "{1}") - @MethodSource - public void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { - try (var arena = Arena.ofConfined()) { - var segment = fuse_conn_info.allocate(arena); - var connInfo = new FuseConnInfoImpl(segment); + @Nested + public class IntFieldTests { - setter.accept(segment, 42); + @DisplayName("test getters") + @ParameterizedTest(name = "{1}") + @MethodSource + void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { + try (var arena = Arena.ofConfined()) { + var segment = fuse_conn_info.allocate(arena); + var connInfo = new FuseConnInfoImpl(segment); - Assertions.assertEquals(42, getter.apply(connInfo)); + setter.accept(segment, 42); + + Assertions.assertEquals(42, getter.apply(connInfo)); + } } - } - public static Stream testGetters() { - return Stream.of( - Arguments.arguments((SetInMemorySegment) fuse_conn_info::proto_major, Named.of("protoMajor()", (GetInConnInfo) FuseConnInfo::protoMajor)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::proto_minor, Named.of("protoMinor()", (GetInConnInfo) FuseConnInfo::protoMinor)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable, Named.of("capable()", (GetInConnInfo) FuseConnInfo::capable)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_write, Named.of("maxWrite()", (GetInConnInfo) FuseConnInfo::maxWrite)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_read, Named.of("maxRead()", (GetInConnInfo) FuseConnInfo::maxRead)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_readahead, Named.of("maxReadahead()", (GetInConnInfo) FuseConnInfo::maxReadahead)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_background, Named.of("maxBackground()", (GetInConnInfo) FuseConnInfo::maxBackground)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::congestion_threshold, Named.of("congestionThreshold()", (GetInConnInfo) FuseConnInfo::congestionThreshold)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::time_gran, Named.of("timeGran()", (GetInConnInfo) FuseConnInfo::timeGran)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::want, Named.of("want()", (GetInConnInfo) FuseConnInfo::want)) - ); + public static Stream testGetters() { + return Stream.of( + Arguments.arguments((SetInMemorySegment) fuse_conn_info::proto_major, Named.of("protoMajor()", (GetInConnInfo) FuseConnInfo::protoMajor)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::proto_minor, Named.of("protoMinor()", (GetInConnInfo) FuseConnInfo::protoMinor)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable, Named.of("capable()", (GetInConnInfo) FuseConnInfo::capable)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_write, Named.of("maxWrite()", (GetInConnInfo) FuseConnInfo::maxWrite)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_read, Named.of("maxRead()", (GetInConnInfo) FuseConnInfo::maxRead)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_readahead, Named.of("maxReadahead()", (GetInConnInfo) FuseConnInfo::maxReadahead)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_background, Named.of("maxBackground()", (GetInConnInfo) FuseConnInfo::maxBackground)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::congestion_threshold, Named.of("congestionThreshold()", (GetInConnInfo) FuseConnInfo::congestionThreshold)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::time_gran, Named.of("timeGran()", (GetInConnInfo) FuseConnInfo::timeGran)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::want, Named.of("want()", (GetInConnInfo) FuseConnInfo::want)) + ); + } + + interface SetInMemorySegment extends GenericSetInMemorySegment { + } + + interface GetInConnInfo extends GenericGetInConnInfo { + } + + @DisplayName("test setters") + @ParameterizedTest(name = "{0}") + @MethodSource + void testSetters(SetInConnInfo setter, GetInMemorySegment getter) { + try (var arena = Arena.ofConfined()) { + var segment = fuse_conn_info.allocate(arena); + var connInfo = new FuseConnInfoImpl(segment); + + setter.accept(connInfo, 42); + + Assertions.assertEquals(42, getter.apply(segment)); + } + } + + public static Stream testSetters() { + return Stream.of( + Arguments.arguments(Named.of("setWant()", (SetInConnInfo) FuseConnInfo::setWant), (GetInMemorySegment) fuse_conn_info::want), + Arguments.arguments(Named.of("setMaxWrite()", (SetInConnInfo) FuseConnInfo::setMaxWrite), (GetInMemorySegment) fuse_conn_info::max_write), + Arguments.arguments(Named.of("setMaxRead()", (SetInConnInfo) FuseConnInfo::setMaxRead), (GetInMemorySegment) fuse_conn_info::max_read), + Arguments.arguments(Named.of("setMaxReadahead()", (SetInConnInfo) FuseConnInfo::setMaxReadahead), (GetInMemorySegment) fuse_conn_info::max_readahead), + Arguments.arguments(Named.of("setMaxBackground()", (SetInConnInfo) FuseConnInfo::setMaxBackground), (GetInMemorySegment) fuse_conn_info::max_background), + Arguments.arguments(Named.of("setCongestionThreshold()", (SetInConnInfo) FuseConnInfo::setCongestionThreshold), (GetInMemorySegment) fuse_conn_info::congestion_threshold), + Arguments.arguments(Named.of("setTimeGran()", (SetInConnInfo) FuseConnInfo::setTimeGran), (GetInMemorySegment) fuse_conn_info::time_gran) + ); + } + + interface SetInConnInfo extends GenericSetInConnInfo { + } + + interface GetInMemorySegment extends GenericGetInMemorySegment { + } } - private interface SetInMemorySegment extends BiConsumer {} + @Nested + public class LongFieldTest { - private interface GetInConnInfo extends Function {} + @DisplayName("test long getters") + @ParameterizedTest(name = "{1}") + @MethodSource + void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { + try (var arena = Arena.ofConfined()) { + var segment = fuse_conn_info.allocate(arena); + var connInfo = new FuseConnInfoImpl(segment); - @DisplayName("test setters") - @ParameterizedTest(name = "{0}") - @MethodSource - public void testSetters(SetInConnInfo setter, GetInMemorySegment getter) { - try (var arena = Arena.ofConfined()) { - var segment = fuse_conn_info.allocate(arena); - var connInfo = new FuseConnInfoImpl(segment); + setter.accept(segment, 42L); - setter.accept(connInfo, 42); + Assertions.assertEquals(42L, getter.apply(connInfo)); + } + } - Assertions.assertEquals(42, getter.apply(segment)); + public static Stream testGetters() { + return Stream.of( + Arguments.arguments((SetInMemorySegment) fuse_conn_info::want_ext, Named.of("wantExt()", (GetInConnInfo) FuseConnInfo::wantExt)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable_ext, Named.of("capable()", (GetInConnInfo) FuseConnInfo::capableExt)) + ); + } + + interface SetInMemorySegment extends GenericSetInMemorySegment { + } + + interface GetInConnInfo extends GenericGetInConnInfo { + } + + @DisplayName("test setters") + @ParameterizedTest(name = "{0}") + @MethodSource + void testSetters(SetInConnInfo setter, GetInMemorySegment getter) { + try (var arena = Arena.ofConfined()) { + var segment = fuse_conn_info.allocate(arena); + var connInfo = new FuseConnInfoImpl(segment); + + setter.accept(connInfo, 42L); + + Assertions.assertEquals(42L, getter.apply(segment)); + } + } + + public static Stream testSetters() { + return Stream.of( + Arguments.arguments(Named.of("setWantExt()", (SetInConnInfo) FuseConnInfo::setWantExt), (GetInMemorySegment) fuse_conn_info::want_ext) + ); + } + + interface SetInConnInfo extends GenericSetInConnInfo { + } + + interface GetInMemorySegment extends GenericGetInMemorySegment { } } - public static Stream testSetters() { - return Stream.of( - Arguments.arguments(Named.of("setWant()", (SetInConnInfo) FuseConnInfo::setWant), (GetInMemorySegment) fuse_conn_info::want), - Arguments.arguments(Named.of("setMaxWrite()", (SetInConnInfo) FuseConnInfo::setMaxWrite), (GetInMemorySegment) fuse_conn_info::max_write), - Arguments.arguments(Named.of("setMaxRead()", (SetInConnInfo) FuseConnInfo::setMaxRead), (GetInMemorySegment) fuse_conn_info::max_read), - Arguments.arguments(Named.of("setMaxReadahead()", (SetInConnInfo) FuseConnInfo::setMaxReadahead), (GetInMemorySegment) fuse_conn_info::max_readahead), - Arguments.arguments(Named.of("setMaxBackground()", (SetInConnInfo) FuseConnInfo::setMaxBackground), (GetInMemorySegment) fuse_conn_info::max_background), - Arguments.arguments(Named.of("setCongestionThreshold()", (SetInConnInfo) FuseConnInfo::setCongestionThreshold), (GetInMemorySegment) fuse_conn_info::congestion_threshold), - Arguments.arguments(Named.of("setTimeGran()", (SetInConnInfo) FuseConnInfo::setTimeGran), (GetInMemorySegment) fuse_conn_info::time_gran) - ); + private interface GenericSetInMemorySegment extends BiConsumer { } - private interface SetInConnInfo extends BiConsumer {} + private interface GenericGetInConnInfo extends Function { + } - private interface GetInMemorySegment extends Function {} + private interface GenericSetInConnInfo extends BiConsumer { + } + private interface GenericGetInMemorySegment extends Function { + } } \ No newline at end of file diff --git a/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImplTest.java b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImplTest.java index 6165d960..b44679c4 100644 --- a/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImplTest.java +++ b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImplTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -17,67 +18,143 @@ public class FuseConnInfoImplTest { - @DisplayName("test getters") - @ParameterizedTest(name = "{1}") - @MethodSource - public void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { - try (var arena = Arena.ofConfined()) { - var segment = fuse_conn_info.allocate(arena); - var connInfo = new FuseConnInfoImpl(segment); + @Nested + public class IntFieldTests { - setter.accept(segment, 42); + @DisplayName("test getters") + @ParameterizedTest(name = "{1}") + @MethodSource + void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { + try (var arena = Arena.ofConfined()) { + var segment = fuse_conn_info.allocate(arena); + var connInfo = new FuseConnInfoImpl(segment); - Assertions.assertEquals(42, getter.apply(connInfo)); + setter.accept(segment, 42); + + Assertions.assertEquals(42, getter.apply(connInfo)); + } } - } - public static Stream testGetters() { - return Stream.of( - Arguments.arguments((SetInMemorySegment) fuse_conn_info::proto_major, Named.of("protoMajor()", (GetInConnInfo) FuseConnInfo::protoMajor)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::proto_minor, Named.of("protoMinor()", (GetInConnInfo) FuseConnInfo::protoMinor)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable, Named.of("capable()", (GetInConnInfo) FuseConnInfo::capable)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_write, Named.of("maxWrite()", (GetInConnInfo) FuseConnInfo::maxWrite)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_read, Named.of("maxRead()", (GetInConnInfo) FuseConnInfo::maxRead)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_readahead, Named.of("maxReadahead()", (GetInConnInfo) FuseConnInfo::maxReadahead)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_background, Named.of("maxBackground()", (GetInConnInfo) FuseConnInfo::maxBackground)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::congestion_threshold, Named.of("congestionThreshold()", (GetInConnInfo) FuseConnInfo::congestionThreshold)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::time_gran, Named.of("timeGran()", (GetInConnInfo) FuseConnInfo::timeGran)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::want, Named.of("want()", (GetInConnInfo) FuseConnInfo::want)) - ); + public static Stream testGetters() { + return Stream.of( + Arguments.arguments((SetInMemorySegment) fuse_conn_info::proto_major, Named.of("protoMajor()", (GetInConnInfo) FuseConnInfo::protoMajor)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::proto_minor, Named.of("protoMinor()", (GetInConnInfo) FuseConnInfo::protoMinor)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable, Named.of("capable()", (GetInConnInfo) FuseConnInfo::capable)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_write, Named.of("maxWrite()", (GetInConnInfo) FuseConnInfo::maxWrite)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_read, Named.of("maxRead()", (GetInConnInfo) FuseConnInfo::maxRead)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_readahead, Named.of("maxReadahead()", (GetInConnInfo) FuseConnInfo::maxReadahead)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_background, Named.of("maxBackground()", (GetInConnInfo) FuseConnInfo::maxBackground)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::congestion_threshold, Named.of("congestionThreshold()", (GetInConnInfo) FuseConnInfo::congestionThreshold)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::time_gran, Named.of("timeGran()", (GetInConnInfo) FuseConnInfo::timeGran)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::want, Named.of("want()", (GetInConnInfo) FuseConnInfo::want)) + ); + } + + interface SetInMemorySegment extends GenericSetInMemorySegment { + } + + interface GetInConnInfo extends GenericGetInConnInfo { + } + + @DisplayName("test setters") + @ParameterizedTest(name = "{0}") + @MethodSource + void testSetters(SetInConnInfo setter, GetInMemorySegment getter) { + try (var arena = Arena.ofConfined()) { + var segment = fuse_conn_info.allocate(arena); + var connInfo = new FuseConnInfoImpl(segment); + + setter.accept(connInfo, 42); + + Assertions.assertEquals(42, getter.apply(segment)); + } + } + + public static Stream testSetters() { + return Stream.of( + Arguments.arguments(Named.of("setWant()", (SetInConnInfo) FuseConnInfo::setWant), (GetInMemorySegment) fuse_conn_info::want), + Arguments.arguments(Named.of("setMaxWrite()", (SetInConnInfo) FuseConnInfo::setMaxWrite), (GetInMemorySegment) fuse_conn_info::max_write), + Arguments.arguments(Named.of("setMaxRead()", (SetInConnInfo) FuseConnInfo::setMaxRead), (GetInMemorySegment) fuse_conn_info::max_read), + Arguments.arguments(Named.of("setMaxReadahead()", (SetInConnInfo) FuseConnInfo::setMaxReadahead), (GetInMemorySegment) fuse_conn_info::max_readahead), + Arguments.arguments(Named.of("setMaxBackground()", (SetInConnInfo) FuseConnInfo::setMaxBackground), (GetInMemorySegment) fuse_conn_info::max_background), + Arguments.arguments(Named.of("setCongestionThreshold()", (SetInConnInfo) FuseConnInfo::setCongestionThreshold), (GetInMemorySegment) fuse_conn_info::congestion_threshold), + Arguments.arguments(Named.of("setTimeGran()", (SetInConnInfo) FuseConnInfo::setTimeGran), (GetInMemorySegment) fuse_conn_info::time_gran) + ); + } + + interface SetInConnInfo extends GenericSetInConnInfo { + } + + interface GetInMemorySegment extends GenericGetInMemorySegment { + } } - private interface SetInMemorySegment extends BiConsumer {} + @Nested + public class LongFieldTest { - private interface GetInConnInfo extends Function {} + @DisplayName("test long getters") + @ParameterizedTest(name = "{1}") + @MethodSource + void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { + try (var arena = Arena.ofConfined()) { + var segment = fuse_conn_info.allocate(arena); + var connInfo = new FuseConnInfoImpl(segment); - @DisplayName("test setters") - @ParameterizedTest(name = "{0}") - @MethodSource - public void testSetters(SetInConnInfo setter, GetInMemorySegment getter) { - try (var arena = Arena.ofConfined()) { - var segment = fuse_conn_info.allocate(arena); - var connInfo = new FuseConnInfoImpl(segment); + setter.accept(segment, 42L); - setter.accept(connInfo, 42); + Assertions.assertEquals(42L, getter.apply(connInfo)); + } + } - Assertions.assertEquals(42, getter.apply(segment)); + public static Stream testGetters() { + return Stream.of( + Arguments.arguments((SetInMemorySegment) fuse_conn_info::want_ext, Named.of("wantExt()", (GetInConnInfo) FuseConnInfo::wantExt)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable_ext, Named.of("capable()", (GetInConnInfo) FuseConnInfo::capableExt)) + ); + } + + interface SetInMemorySegment extends GenericSetInMemorySegment { + } + + interface GetInConnInfo extends GenericGetInConnInfo { + } + + @DisplayName("test setters") + @ParameterizedTest(name = "{0}") + @MethodSource + void testSetters(SetInConnInfo setter, GetInMemorySegment getter) { + try (var arena = Arena.ofConfined()) { + var segment = fuse_conn_info.allocate(arena); + var connInfo = new FuseConnInfoImpl(segment); + + setter.accept(connInfo, 42L); + + Assertions.assertEquals(42L, getter.apply(segment)); + } + } + + public static Stream testSetters() { + return Stream.of( + Arguments.arguments(Named.of("setWantExt()", (SetInConnInfo) FuseConnInfo::setWantExt), (GetInMemorySegment) fuse_conn_info::want_ext) + ); + } + + interface SetInConnInfo extends GenericSetInConnInfo { + } + + interface GetInMemorySegment extends GenericGetInMemorySegment { } } - public static Stream testSetters() { - return Stream.of( - Arguments.arguments(Named.of("setWant()", (SetInConnInfo) FuseConnInfo::setWant), (GetInMemorySegment) fuse_conn_info::want), - Arguments.arguments(Named.of("setMaxWrite()", (SetInConnInfo) FuseConnInfo::setMaxWrite), (GetInMemorySegment) fuse_conn_info::max_write), - Arguments.arguments(Named.of("setMaxRead()", (SetInConnInfo) FuseConnInfo::setMaxRead), (GetInMemorySegment) fuse_conn_info::max_read), - Arguments.arguments(Named.of("setMaxReadahead()", (SetInConnInfo) FuseConnInfo::setMaxReadahead), (GetInMemorySegment) fuse_conn_info::max_readahead), - Arguments.arguments(Named.of("setMaxBackground()", (SetInConnInfo) FuseConnInfo::setMaxBackground), (GetInMemorySegment) fuse_conn_info::max_background), - Arguments.arguments(Named.of("setCongestionThreshold()", (SetInConnInfo) FuseConnInfo::setCongestionThreshold), (GetInMemorySegment) fuse_conn_info::congestion_threshold), - Arguments.arguments(Named.of("setTimeGran()", (SetInConnInfo) FuseConnInfo::setTimeGran), (GetInMemorySegment) fuse_conn_info::time_gran) - ); + private interface GenericSetInMemorySegment extends BiConsumer { } - private interface SetInConnInfo extends BiConsumer {} + private interface GenericGetInConnInfo extends Function { + } - private interface GetInMemorySegment extends Function {} + private interface GenericSetInConnInfo extends BiConsumer { + } + private interface GenericGetInMemorySegment extends Function { + } } \ No newline at end of file From 71dbfa44d902cbe0541e7b7a27c07605ab5b4073 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 10 Sep 2025 14:19:26 +0200 Subject: [PATCH 05/16] add more semi offical javadoc tags --- pom.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cc0f86a7..a73653b2 100644 --- a/pom.xml +++ b/pom.xml @@ -126,7 +126,16 @@ a API Note: - + + implSpec + a + Implementation Requirements: + + + implNote + a + Implementation Note: + From a0f9c4ceeb64065295ee76d8f74fe697cb1e0147 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 12 Sep 2025 16:18:14 +0200 Subject: [PATCH 06/16] Fix wrong beginning of try statement in FuseFunctions --- .../jfuse/linux/aarch64/FuseFunctions.java | 4 +- .../jfuse/linux/aarch64/FuseImpl.java | 6 +- .../jfuse/linux/aarch64/FuseImplTest.java | 60 ++++++++++++------- .../jfuse/linux/amd64/FuseFunctions.java | 4 +- .../jfuse/linux/amd64/FuseImpl.java | 6 +- .../jfuse/linux/amd64/FuseImplTest.java | 55 ++++++++++++----- 6 files changed, 93 insertions(+), 42 deletions(-) diff --git a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseFunctions.java b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseFunctions.java index c0439ff3..59a92d6d 100644 --- a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseFunctions.java +++ b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseFunctions.java @@ -53,8 +53,8 @@ public static int fuse_parse_cmdline(MemorySegment args, MemorySegment opts) { } public static boolean fuse_set_feature_flag(MemorySegment fuse_conn_info, long flag) throws UnsupportedOperationException { + var method = Holder.INSTANCE.fuse_set_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not support fuse_set_feature_flag")); try { - var method = Holder.INSTANCE.fuse_set_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not support fuse_set_feature_flag")); return ((int) method.invokeExact(fuse_conn_info, flag)) != 0; } catch (Throwable e) { throw new AssertionError("should not reach here", e); @@ -62,8 +62,8 @@ public static boolean fuse_set_feature_flag(MemorySegment fuse_conn_info, long f } public static void fuse_unset_feature_flag(MemorySegment fuse_conn_info, long flag) throws UnsupportedOperationException { + var method = Holder.INSTANCE.fuse_unset_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not support fuse_unset_feature_flag")); try { - var method = Holder.INSTANCE.fuse_unset_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not support fuse_unset_feature_flag")); method.invokeExact(fuse_conn_info, flag); } catch (Throwable e) { throw new AssertionError("should not reach here", e); diff --git a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseImpl.java b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseImpl.java index e529f481..75b5ee22 100644 --- a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseImpl.java +++ b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseImpl.java @@ -109,7 +109,11 @@ protected void bind(FuseOperations.Operation operation) { @VisibleForTesting MemorySegment init(MemorySegment conn, MemorySegment cfg) { var connInfo = new FuseConnInfoImpl(conn); - connInfo.setWant(connInfo.want() | FuseConnInfo.FUSE_CAP_READDIRPLUS); + try { + connInfo.setFeatureFlag(FuseConnInfo.FUSE_CAP_READDIRPLUS); + } catch (UnsupportedOperationException _) { + connInfo.setWant(connInfo.want() | FuseConnInfo.FUSE_CAP_READDIRPLUS); + } var config = new FuseConfigImpl(cfg); fuseOperations.init(connInfo, config); return MemorySegment.NULL; diff --git a/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseImplTest.java b/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseImplTest.java index 626d26a8..b1e424d4 100644 --- a/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseImplTest.java +++ b/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseImplTest.java @@ -4,15 +4,13 @@ import org.cryptomator.jfuse.api.FuseMountFailedException; import org.cryptomator.jfuse.api.FuseOperations; import org.cryptomator.jfuse.api.TimeSpec; -import org.cryptomator.jfuse.linux.aarch64.extr.fuse3_lowlevel.fuse_cmdline_opts; import org.cryptomator.jfuse.linux.aarch64.extr.fuse3.fuse_config; import org.cryptomator.jfuse.linux.aarch64.extr.fuse3.fuse_conn_info; import org.cryptomator.jfuse.linux.aarch64.extr.fuse3.fuse_file_info; import org.cryptomator.jfuse.linux.aarch64.extr.fuse3.fuse_h; import org.cryptomator.jfuse.linux.aarch64.extr.fuse3.timespec; -import org.junit.jupiter.api.AfterEach; +import org.cryptomator.jfuse.linux.aarch64.extr.fuse3_lowlevel.fuse_cmdline_opts; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -20,7 +18,6 @@ import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Answers; -import org.mockito.MockedStatic; import org.mockito.Mockito; import java.lang.foreign.Arena; @@ -162,22 +159,45 @@ public void testFsyncdir() { } - @DisplayName("init() sets fuse_conn_info.wants |= FUSE_CAP_READDIRPLUS") - @Test - public void testInit() { - try (var arena = Arena.ofConfined()) { - var result = new AtomicInteger(); - Mockito.doAnswer(invocation -> { - FuseConnInfo connInfo = invocation.getArgument(0); - result.set(connInfo.want()); - return null; - }).when(fuseOps).init(Mockito.any(), Mockito.any()); - var connInfo = fuse_conn_info.allocate(arena); - var fuseConfig = fuse_config.allocate(arena); - - fuseImpl.init(connInfo, fuseConfig); - - Assertions.assertEquals(FuseConnInfo.FUSE_CAP_READDIRPLUS, result.get() & FuseConnInfo.FUSE_CAP_READDIRPLUS); + @Nested + @DisplayName("init") + public class Init { + + @DisplayName("init() sets fuse_conn_info.wants |= FUSE_CAP_READDIRPLUS for libfuse < 3.17") + @Test + public void testInit316() { + try (var fuseFunctionsClass = Mockito.mockStatic(FuseFunctions.class); + var arena = Arena.ofConfined()) { + fuseFunctionsClass.when(() -> FuseFunctions.fuse_set_feature_flag(Mockito.any(), Mockito.anyLong())).thenThrow(UnsupportedOperationException.class); + var result = new AtomicInteger(); + Mockito.doAnswer(invocation -> { + FuseConnInfo connInfo = invocation.getArgument(0); + result.set(connInfo.want()); + return null; + }).when(fuseOps).init(Mockito.any(), Mockito.any()); + var connInfo = fuse_conn_info.allocate(arena); + var fuseConfig = fuse_config.allocate(arena); + + fuseImpl.init(connInfo, fuseConfig); + + Assertions.assertEquals(FuseConnInfo.FUSE_CAP_READDIRPLUS, result.get() & FuseConnInfo.FUSE_CAP_READDIRPLUS); + } + } + + @DisplayName("init() calls set_feature_flag(FUSE_CAP_READDIRPLUS) for libfuse >= 3.17") + @Test + public void testInit317() { + try (var fuseFunctionsClass = Mockito.mockStatic(FuseFunctions.class); + var arena = Arena.ofConfined()) { + var connInfo = fuse_conn_info.allocate(arena); + var fuseConfig = fuse_config.allocate(arena); + + fuseFunctionsClass.when(() -> FuseFunctions.fuse_set_feature_flag(connInfo, FuseConnInfo.FUSE_CAP_READDIRPLUS)).thenReturn(true); + + fuseImpl.init(connInfo, fuseConfig); + + fuseFunctionsClass.verify(() -> FuseFunctions.fuse_set_feature_flag(connInfo, FuseConnInfo.FUSE_CAP_READDIRPLUS)); + } } } diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java index 817f2a06..edd2393e 100644 --- a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java @@ -53,8 +53,8 @@ public static int fuse_parse_cmdline(MemorySegment args, MemorySegment opts) { } public static boolean fuse_set_feature_flag(MemorySegment fuse_conn_info, long flag) throws UnsupportedOperationException { + var method = Holder.INSTANCE.fuse_set_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not implement fuse_set_feature_flag")); try { - var method = Holder.INSTANCE.fuse_set_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not support fuse_set_feature_flag")); return ((int) method.invokeExact(fuse_conn_info, flag)) != 0; } catch (Throwable e) { throw new AssertionError("should not reach here", e); @@ -62,8 +62,8 @@ public static boolean fuse_set_feature_flag(MemorySegment fuse_conn_info, long f } public static void fuse_unset_feature_flag(MemorySegment fuse_conn_info, long flag) throws UnsupportedOperationException { + var method = Holder.INSTANCE.fuse_unset_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not implement fuse_unset_feature_flag")); try { - var method = Holder.INSTANCE.fuse_unset_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not support fuse_unset_feature_flag")); method.invokeExact(fuse_conn_info, flag); } catch (Throwable e) { throw new AssertionError("should not reach here", e); diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java index 43ea654e..004c82cb 100644 --- a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java @@ -109,7 +109,11 @@ protected void bind(FuseOperations.Operation operation) { @VisibleForTesting MemorySegment init(MemorySegment conn, MemorySegment cfg) { var connInfo = new FuseConnInfoImpl(conn); - connInfo.setWant(connInfo.want() | FuseConnInfo.FUSE_CAP_READDIRPLUS); + try { + connInfo.setFeatureFlag(FuseConnInfo.FUSE_CAP_READDIRPLUS); + } catch (UnsupportedOperationException _) { + connInfo.setWant(connInfo.want() | FuseConnInfo.FUSE_CAP_READDIRPLUS); + } var config = new FuseConfigImpl(cfg); fuseOperations.init(connInfo, config); return MemorySegment.NULL; diff --git a/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseImplTest.java b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseImplTest.java index d93b71b3..572d160a 100644 --- a/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseImplTest.java +++ b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseImplTest.java @@ -159,22 +159,45 @@ public void testFsyncdir() { } - @DisplayName("init() sets fuse_conn_info.wants |= FUSE_CAP_READDIRPLUS") - @Test - public void testInit() { - try (var arena = Arena.ofConfined()) { - var result = new AtomicInteger(); - Mockito.doAnswer(invocation -> { - FuseConnInfo connInfo = invocation.getArgument(0); - result.set(connInfo.want()); - return null; - }).when(fuseOps).init(Mockito.any(), Mockito.any()); - var connInfo = fuse_conn_info.allocate(arena); - var fuseConfig = fuse_config.allocate(arena); - - fuseImpl.init(connInfo, fuseConfig); - - Assertions.assertEquals(FuseConnInfo.FUSE_CAP_READDIRPLUS, result.get() & FuseConnInfo.FUSE_CAP_READDIRPLUS); + @Nested + @DisplayName("init") + public class Init { + + @DisplayName("init() sets fuse_conn_info.wants |= FUSE_CAP_READDIRPLUS for libfuse < 3.17") + @Test + public void testInit316() { + try (var fuseFunctionsClass = Mockito.mockStatic(FuseFunctions.class); + var arena = Arena.ofConfined()) { + fuseFunctionsClass.when(() -> FuseFunctions.fuse_set_feature_flag(Mockito.any(), Mockito.anyLong())).thenThrow(UnsupportedOperationException.class); + var result = new AtomicInteger(); + Mockito.doAnswer(invocation -> { + FuseConnInfo connInfo = invocation.getArgument(0); + result.set(connInfo.want()); + return null; + }).when(fuseOps).init(Mockito.any(), Mockito.any()); + var connInfo = fuse_conn_info.allocate(arena); + var fuseConfig = fuse_config.allocate(arena); + + fuseImpl.init(connInfo, fuseConfig); + + Assertions.assertEquals(FuseConnInfo.FUSE_CAP_READDIRPLUS, result.get() & FuseConnInfo.FUSE_CAP_READDIRPLUS); + } + } + + @DisplayName("init() calls set_feature_flag(FUSE_CAP_READDIRPLUS) for libfuse >= 3.17") + @Test + public void testInit317() { + try (var fuseFunctionsClass = Mockito.mockStatic(FuseFunctions.class); + var arena = Arena.ofConfined()) { + var connInfo = fuse_conn_info.allocate(arena); + var fuseConfig = fuse_config.allocate(arena); + + fuseFunctionsClass.when(() -> FuseFunctions.fuse_set_feature_flag(connInfo, FuseConnInfo.FUSE_CAP_READDIRPLUS)).thenReturn(true); + + fuseImpl.init(connInfo, fuseConfig); + + fuseFunctionsClass.verify(() -> FuseFunctions.fuse_set_feature_flag(connInfo, FuseConnInfo.FUSE_CAP_READDIRPLUS)); + } } } From aed1dd2bd4fe802b7c13d1d4f1c3e13a553ba1e3 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 15 Sep 2025 11:06:25 +0200 Subject: [PATCH 07/16] Adjust API * add fuse_get_feature_flag * throw UnsupportedOperationException by default --- .../cryptomator/jfuse/api/FuseConnInfo.java | 23 +++++++++++++++---- .../jfuse/linux/amd64/FuseFunctions.java | 13 +++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseConnInfo.java b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseConnInfo.java index 63453e44..2205d2e8 100644 --- a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseConnInfo.java +++ b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseConnInfo.java @@ -555,22 +555,35 @@ default void setWantExt(long wantExt) { * @param flag feature flag to be set * @return {@code true} if the flag was set, {@code false} if the flag is not supported * @throws UnsupportedOperationException if the loaded fuse library does not implement the method - * @implSpec The default implementation discards the input and simply returns false. + * @implSpec The default implementation throws {@link UnsupportedOperationException} * @since libFUSE 3.17 */ default boolean setFeatureFlag(long flag) throws UnsupportedOperationException { - return false; + throw new UnsupportedOperationException("Loaded library does not implement fuse_set_feature_flag"); } /** - * Unset a feature flag in the want_ext field of fuse_conn_info. Does nothing if the method is not implemented + * Unset a feature flag in the want_ext field of fuse_conn_info. * * @param flag feature flag to be unset * @throws UnsupportedOperationException if the loaded fuse library does not implement the method - * @implSpec The default implementation is no-op. + * @implSpec The default implementation throws {@link UnsupportedOperationException} * @since libFUSE 3.17 */ default void unsetFeatureFlag(long flag) throws UnsupportedOperationException { - //no-op + throw new UnsupportedOperationException("Loaded library does not implement fuse_unset_feature_flag"); + } + + /** + * Get the value of a feature flag in the want_ext field of fuse_conn_info. + * + * @param flag feature flag to be checked + * @return {@code true} if the flag is set, {@code false} otherwise + * @throws UnsupportedOperationException if the loaded fuse library does not implement the method + * @implSpec The default implementation throws {@link UnsupportedOperationException} + * @since libFUSE 3.17 + */ + default boolean getFeatureFlag(long flag) throws UnsupportedOperationException { + throw new UnsupportedOperationException("Loaded library does not implement fuse_unset_feature_flag"); } } diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java index edd2393e..b26eeb25 100644 --- a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java @@ -23,10 +23,12 @@ class FuseFunctions { //https://github.com/libfuse/libfuse/blob/fuse-3.17.4/lib/fuse_lowlevel.c#L2035 private static final FunctionDescriptor FUSE_SET_FEATURE_FLAG = FunctionDescriptor.of(ADDRESS, JAVA_LONG); private static final FunctionDescriptor FUSE_UNSET_FEATURE_FLAG = FunctionDescriptor.of(ADDRESS, JAVA_LONG); + private static final FunctionDescriptor FUSE_GET_FEATURE_FLAG = FunctionDescriptor.of(ADDRESS, JAVA_LONG); private final MethodHandle fuse_parse_cmdline; private final Optional fuse_set_feature_flag; private final Optional fuse_unset_feature_flag; + private final Optional fuse_get_feature_flag; private FuseFunctions() { var lookup = SymbolLookup.loaderLookup(); @@ -38,6 +40,8 @@ private FuseFunctions() { .map(symbol -> linker.downcallHandle(symbol, FUSE_SET_FEATURE_FLAG)); this.fuse_unset_feature_flag = lookup.find("fuse_unset_feature_flag") .map(symbol -> linker.downcallHandle(symbol, FUSE_UNSET_FEATURE_FLAG)); + this.fuse_get_feature_flag = lookup.find("fuse_get_feature_flag") + .map(symbol -> linker.downcallHandle(symbol, FUSE_GET_FEATURE_FLAG)); } private static class Holder { @@ -70,4 +74,13 @@ public static void fuse_unset_feature_flag(MemorySegment fuse_conn_info, long fl } } + public static boolean fuse_get_feature_flag(MemorySegment fuse_conn_info, long flag) throws UnsupportedOperationException { + var method = Holder.INSTANCE.fuse_get_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not implement fuse_get_feature_flag")); + try { + return ((int) method.invokeExact(fuse_conn_info, flag)) != 0; + } catch (Throwable e) { + throw new AssertionError("should not reach here", e); + } + } + } From 3912140b96d15861d70484a52a19e516ee87d58e Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 15 Sep 2025 11:08:20 +0200 Subject: [PATCH 08/16] check fuse version instead of probing --- .../main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java | 4 ++-- .../src/main/java/org/cryptomator/jfuse/win/FuseImpl.java | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java index 004c82cb..edc2b338 100644 --- a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java @@ -109,9 +109,9 @@ protected void bind(FuseOperations.Operation operation) { @VisibleForTesting MemorySegment init(MemorySegment conn, MemorySegment cfg) { var connInfo = new FuseConnInfoImpl(conn); - try { + if(connInfo.protoMinor() >= 17 ) { connInfo.setFeatureFlag(FuseConnInfo.FUSE_CAP_READDIRPLUS); - } catch (UnsupportedOperationException _) { + } else { connInfo.setWant(connInfo.want() | FuseConnInfo.FUSE_CAP_READDIRPLUS); } var config = new FuseConfigImpl(cfg); diff --git a/jfuse-win/src/main/java/org/cryptomator/jfuse/win/FuseImpl.java b/jfuse-win/src/main/java/org/cryptomator/jfuse/win/FuseImpl.java index e85e93ad..03bc4205 100644 --- a/jfuse-win/src/main/java/org/cryptomator/jfuse/win/FuseImpl.java +++ b/jfuse-win/src/main/java/org/cryptomator/jfuse/win/FuseImpl.java @@ -14,7 +14,6 @@ import org.jetbrains.annotations.VisibleForTesting; import java.lang.foreign.Arena; -import java.lang.foreign.MemoryLayout; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; import java.nio.file.Path; From 4fecb5f7a0f1fe02521a60b3686fe602392b35aa Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 15 Sep 2025 12:32:12 +0200 Subject: [PATCH 09/16] prevent jvm crashes when using trying to access want_ext for libfuse versions < 3.17 * change FuseConnInfoImpl to class * add subclass FuseConnInfoImpl317 adding actual implementation of fields and functions * adjust tests --- .../jfuse/linux/amd64/FuseConnInfoImpl.java | 37 ++-- .../linux/amd64/FuseConnInfoImpl317.java | 45 +++++ .../jfuse/linux/amd64/FuseImpl.java | 1 + .../linux/amd64/FuseConnInfoImpl317Test.java | 72 ++++++++ .../linux/amd64/FuseConnInfoImplTest.java | 170 +++++------------- .../jfuse/linux/amd64/FuseImplTest.java | 18 +- 6 files changed, 191 insertions(+), 152 deletions(-) create mode 100644 jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl317.java create mode 100644 jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl317Test.java diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl.java index 14e7a20e..60192570 100644 --- a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl.java +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl.java @@ -5,7 +5,18 @@ import java.lang.foreign.MemorySegment; -record FuseConnInfoImpl(MemorySegment segment) implements FuseConnInfo { +class FuseConnInfoImpl implements FuseConnInfo { + + protected MemorySegment segment; + + FuseConnInfoImpl(MemorySegment segment) { + this.segment = segment; + } + + //mimic record behaviour + public MemorySegment segment() { + return segment; + } @Override public int protoMajor() { @@ -92,28 +103,4 @@ public void setTimeGran(int timeGran) { fuse_conn_info.time_gran(segment, timeGran); } - @Override - public long capableExt() { - return fuse_conn_info.capable_ext(segment); - } - - @Override - public long wantExt() { - return fuse_conn_info.want_ext(segment); - } - - @Override - public void setWantExt(long wantExt) { - fuse_conn_info.want_ext(segment, wantExt); - } - - @Override - public boolean setFeatureFlag(long flag) throws UnsupportedOperationException { - return FuseFunctions.fuse_set_feature_flag(segment, flag); - } - - @Override - public void unsetFeatureFlag(long flag) throws UnsupportedOperationException { - FuseFunctions.fuse_unset_feature_flag(segment, flag); - } } diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl317.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl317.java new file mode 100644 index 00000000..44a23568 --- /dev/null +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl317.java @@ -0,0 +1,45 @@ +package org.cryptomator.jfuse.linux.amd64; + +import org.cryptomator.jfuse.linux.amd64.extr.fuse3.fuse_conn_info; + +import java.lang.foreign.MemorySegment; + +class FuseConnInfoImpl317 extends FuseConnInfoImpl { + + FuseConnInfoImpl317(MemorySegment segment) { + super(segment); + } + + @Override + public long capableExt() { + //old school style + //return segment.get(fuse_conn_info.capable_ext$layout().withByteAlignment(1), fuse_conn_info.capable_ext$offset()); + return fuse_conn_info.capable_ext(segment); + } + + @Override + public long wantExt() { + return fuse_conn_info.want_ext(segment); + } + + @Override + public void setWantExt(long wantExt) { + fuse_conn_info.want_ext(segment, wantExt); + } + + @Override + public boolean setFeatureFlag(long flag) { + return FuseFunctions.fuse_set_feature_flag(segment, flag); + } + + @Override + public void unsetFeatureFlag(long flag) { + FuseFunctions.fuse_unset_feature_flag(segment, flag); + } + + @Override + public boolean getFeatureFlag(long flag) { + return FuseFunctions.fuse_get_feature_flag(segment, flag); + } + +} diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java index edc2b338..ec57004c 100644 --- a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java @@ -110,6 +110,7 @@ protected void bind(FuseOperations.Operation operation) { MemorySegment init(MemorySegment conn, MemorySegment cfg) { var connInfo = new FuseConnInfoImpl(conn); if(connInfo.protoMinor() >= 17 ) { + connInfo = new FuseConnInfoImpl317(conn); connInfo.setFeatureFlag(FuseConnInfo.FUSE_CAP_READDIRPLUS); } else { connInfo.setWant(connInfo.want() | FuseConnInfo.FUSE_CAP_READDIRPLUS); diff --git a/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl317Test.java b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl317Test.java new file mode 100644 index 00000000..2ae46d43 --- /dev/null +++ b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl317Test.java @@ -0,0 +1,72 @@ +package org.cryptomator.jfuse.linux.amd64; + +import org.cryptomator.jfuse.api.FuseConnInfo; +import org.cryptomator.jfuse.linux.amd64.extr.fuse3.fuse_conn_info; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.stream.Stream; + +public class FuseConnInfoImpl317Test { + + @DisplayName("test long getters") + @ParameterizedTest(name = "{1}") + @MethodSource + void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { + try (var arena = Arena.ofConfined()) { + var segment = fuse_conn_info.allocate(arena); + var connInfo = new FuseConnInfoImpl317(segment); + + setter.accept(segment, 42L); + + Assertions.assertEquals(42L, getter.apply(connInfo)); + } + } + + public static Stream testGetters() { + return Stream.of( + Arguments.arguments((SetInMemorySegment) fuse_conn_info::want_ext, Named.of("wantExt()", (GetInConnInfo) FuseConnInfo::wantExt)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable_ext, Named.of("capable()", (GetInConnInfo) FuseConnInfo::capableExt)) + ); + } + + interface SetInMemorySegment extends BiConsumer { + } + + interface GetInConnInfo extends Function { + } + + @DisplayName("test setters") + @ParameterizedTest(name = "{0}") + @MethodSource + void testSetters(SetInConnInfo setter, GetInMemorySegment getter) { + try (var arena = Arena.ofConfined()) { + var segment = fuse_conn_info.allocate(arena); + var connInfo = new FuseConnInfoImpl317(segment); + + setter.accept(connInfo, 42L); + + Assertions.assertEquals(42L, getter.apply(segment)); + } + } + + public static Stream testSetters() { + return Stream.of( + Arguments.arguments(Named.of("setWantExt()", (SetInConnInfo) FuseConnInfo::setWantExt), (GetInMemorySegment) fuse_conn_info::want_ext) + ); + } + + interface SetInConnInfo extends BiConsumer { + } + + interface GetInMemorySegment extends Function { + } +} diff --git a/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImplTest.java b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImplTest.java index b44679c4..36b789d9 100644 --- a/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImplTest.java +++ b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImplTest.java @@ -5,7 +5,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Named; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -18,143 +17,70 @@ public class FuseConnInfoImplTest { - @Nested - public class IntFieldTests { + @DisplayName("test getters") + @ParameterizedTest(name = "{1}") + @MethodSource + void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { + try (var arena = Arena.ofConfined()) { + var segment = fuse_conn_info.allocate(arena); + var connInfo = new FuseConnInfoImpl(segment); - @DisplayName("test getters") - @ParameterizedTest(name = "{1}") - @MethodSource - void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { - try (var arena = Arena.ofConfined()) { - var segment = fuse_conn_info.allocate(arena); - var connInfo = new FuseConnInfoImpl(segment); + setter.accept(segment, 42); - setter.accept(segment, 42); - - Assertions.assertEquals(42, getter.apply(connInfo)); - } - } - - public static Stream testGetters() { - return Stream.of( - Arguments.arguments((SetInMemorySegment) fuse_conn_info::proto_major, Named.of("protoMajor()", (GetInConnInfo) FuseConnInfo::protoMajor)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::proto_minor, Named.of("protoMinor()", (GetInConnInfo) FuseConnInfo::protoMinor)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable, Named.of("capable()", (GetInConnInfo) FuseConnInfo::capable)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_write, Named.of("maxWrite()", (GetInConnInfo) FuseConnInfo::maxWrite)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_read, Named.of("maxRead()", (GetInConnInfo) FuseConnInfo::maxRead)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_readahead, Named.of("maxReadahead()", (GetInConnInfo) FuseConnInfo::maxReadahead)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_background, Named.of("maxBackground()", (GetInConnInfo) FuseConnInfo::maxBackground)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::congestion_threshold, Named.of("congestionThreshold()", (GetInConnInfo) FuseConnInfo::congestionThreshold)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::time_gran, Named.of("timeGran()", (GetInConnInfo) FuseConnInfo::timeGran)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::want, Named.of("want()", (GetInConnInfo) FuseConnInfo::want)) - ); - } - - interface SetInMemorySegment extends GenericSetInMemorySegment { - } - - interface GetInConnInfo extends GenericGetInConnInfo { - } - - @DisplayName("test setters") - @ParameterizedTest(name = "{0}") - @MethodSource - void testSetters(SetInConnInfo setter, GetInMemorySegment getter) { - try (var arena = Arena.ofConfined()) { - var segment = fuse_conn_info.allocate(arena); - var connInfo = new FuseConnInfoImpl(segment); - - setter.accept(connInfo, 42); - - Assertions.assertEquals(42, getter.apply(segment)); - } - } - - public static Stream testSetters() { - return Stream.of( - Arguments.arguments(Named.of("setWant()", (SetInConnInfo) FuseConnInfo::setWant), (GetInMemorySegment) fuse_conn_info::want), - Arguments.arguments(Named.of("setMaxWrite()", (SetInConnInfo) FuseConnInfo::setMaxWrite), (GetInMemorySegment) fuse_conn_info::max_write), - Arguments.arguments(Named.of("setMaxRead()", (SetInConnInfo) FuseConnInfo::setMaxRead), (GetInMemorySegment) fuse_conn_info::max_read), - Arguments.arguments(Named.of("setMaxReadahead()", (SetInConnInfo) FuseConnInfo::setMaxReadahead), (GetInMemorySegment) fuse_conn_info::max_readahead), - Arguments.arguments(Named.of("setMaxBackground()", (SetInConnInfo) FuseConnInfo::setMaxBackground), (GetInMemorySegment) fuse_conn_info::max_background), - Arguments.arguments(Named.of("setCongestionThreshold()", (SetInConnInfo) FuseConnInfo::setCongestionThreshold), (GetInMemorySegment) fuse_conn_info::congestion_threshold), - Arguments.arguments(Named.of("setTimeGran()", (SetInConnInfo) FuseConnInfo::setTimeGran), (GetInMemorySegment) fuse_conn_info::time_gran) - ); - } - - interface SetInConnInfo extends GenericSetInConnInfo { - } - - interface GetInMemorySegment extends GenericGetInMemorySegment { + Assertions.assertEquals(42, getter.apply(connInfo)); } } - @Nested - public class LongFieldTest { - - @DisplayName("test long getters") - @ParameterizedTest(name = "{1}") - @MethodSource - void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { - try (var arena = Arena.ofConfined()) { - var segment = fuse_conn_info.allocate(arena); - var connInfo = new FuseConnInfoImpl(segment); - - setter.accept(segment, 42L); - - Assertions.assertEquals(42L, getter.apply(connInfo)); - } - } - - public static Stream testGetters() { - return Stream.of( - Arguments.arguments((SetInMemorySegment) fuse_conn_info::want_ext, Named.of("wantExt()", (GetInConnInfo) FuseConnInfo::wantExt)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable_ext, Named.of("capable()", (GetInConnInfo) FuseConnInfo::capableExt)) - ); - } - - interface SetInMemorySegment extends GenericSetInMemorySegment { - } - - interface GetInConnInfo extends GenericGetInConnInfo { - } + public static Stream testGetters() { + return Stream.of( + Arguments.arguments((SetInMemorySegment) fuse_conn_info::proto_major, Named.of("protoMajor()", (GetInConnInfo) FuseConnInfo::protoMajor)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::proto_minor, Named.of("protoMinor()", (GetInConnInfo) FuseConnInfo::protoMinor)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable, Named.of("capable()", (GetInConnInfo) FuseConnInfo::capable)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_write, Named.of("maxWrite()", (GetInConnInfo) FuseConnInfo::maxWrite)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_read, Named.of("maxRead()", (GetInConnInfo) FuseConnInfo::maxRead)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_readahead, Named.of("maxReadahead()", (GetInConnInfo) FuseConnInfo::maxReadahead)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_background, Named.of("maxBackground()", (GetInConnInfo) FuseConnInfo::maxBackground)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::congestion_threshold, Named.of("congestionThreshold()", (GetInConnInfo) FuseConnInfo::congestionThreshold)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::time_gran, Named.of("timeGran()", (GetInConnInfo) FuseConnInfo::timeGran)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::want, Named.of("want()", (GetInConnInfo) FuseConnInfo::want)) + ); + } - @DisplayName("test setters") - @ParameterizedTest(name = "{0}") - @MethodSource - void testSetters(SetInConnInfo setter, GetInMemorySegment getter) { - try (var arena = Arena.ofConfined()) { - var segment = fuse_conn_info.allocate(arena); - var connInfo = new FuseConnInfoImpl(segment); + interface SetInMemorySegment extends BiConsumer { + } - setter.accept(connInfo, 42L); + interface GetInConnInfo extends Function { + } - Assertions.assertEquals(42L, getter.apply(segment)); - } - } + @DisplayName("test setters") + @ParameterizedTest(name = "{0}") + @MethodSource + void testSetters(SetInConnInfo setter, GetInMemorySegment getter) { + try (var arena = Arena.ofConfined()) { + var segment = fuse_conn_info.allocate(arena); + var connInfo = new FuseConnInfoImpl(segment); - public static Stream testSetters() { - return Stream.of( - Arguments.arguments(Named.of("setWantExt()", (SetInConnInfo) FuseConnInfo::setWantExt), (GetInMemorySegment) fuse_conn_info::want_ext) - ); - } + setter.accept(connInfo, 42); - interface SetInConnInfo extends GenericSetInConnInfo { + Assertions.assertEquals(42, getter.apply(segment)); } - - interface GetInMemorySegment extends GenericGetInMemorySegment { - } - } - - private interface GenericSetInMemorySegment extends BiConsumer { } - private interface GenericGetInConnInfo extends Function { + public static Stream testSetters() { + return Stream.of( + Arguments.arguments(Named.of("setWant()", (SetInConnInfo) FuseConnInfo::setWant), (GetInMemorySegment) fuse_conn_info::want), + Arguments.arguments(Named.of("setMaxWrite()", (SetInConnInfo) FuseConnInfo::setMaxWrite), (GetInMemorySegment) fuse_conn_info::max_write), + Arguments.arguments(Named.of("setMaxRead()", (SetInConnInfo) FuseConnInfo::setMaxRead), (GetInMemorySegment) fuse_conn_info::max_read), + Arguments.arguments(Named.of("setMaxReadahead()", (SetInConnInfo) FuseConnInfo::setMaxReadahead), (GetInMemorySegment) fuse_conn_info::max_readahead), + Arguments.arguments(Named.of("setMaxBackground()", (SetInConnInfo) FuseConnInfo::setMaxBackground), (GetInMemorySegment) fuse_conn_info::max_background), + Arguments.arguments(Named.of("setCongestionThreshold()", (SetInConnInfo) FuseConnInfo::setCongestionThreshold), (GetInMemorySegment) fuse_conn_info::congestion_threshold), + Arguments.arguments(Named.of("setTimeGran()", (SetInConnInfo) FuseConnInfo::setTimeGran), (GetInMemorySegment) fuse_conn_info::time_gran) + ); } - private interface GenericSetInConnInfo extends BiConsumer { + interface SetInConnInfo extends BiConsumer { } - private interface GenericGetInMemorySegment extends Function { + interface GetInMemorySegment extends Function { } } \ No newline at end of file diff --git a/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseImplTest.java b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseImplTest.java index 572d160a..7ff8ce01 100644 --- a/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseImplTest.java +++ b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseImplTest.java @@ -24,7 +24,7 @@ import java.lang.foreign.MemorySegment; import java.time.Instant; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; public class FuseImplTest { @@ -169,18 +169,19 @@ public void testInit316() { try (var fuseFunctionsClass = Mockito.mockStatic(FuseFunctions.class); var arena = Arena.ofConfined()) { fuseFunctionsClass.when(() -> FuseFunctions.fuse_set_feature_flag(Mockito.any(), Mockito.anyLong())).thenThrow(UnsupportedOperationException.class); - var result = new AtomicInteger(); + var consumerRecievedConnInfo = new AtomicReference(); Mockito.doAnswer(invocation -> { - FuseConnInfo connInfo = invocation.getArgument(0); - result.set(connInfo.want()); + consumerRecievedConnInfo.set(invocation.getArgument(0)); return null; }).when(fuseOps).init(Mockito.any(), Mockito.any()); var connInfo = fuse_conn_info.allocate(arena); + fuse_conn_info.proto_minor(connInfo, 16); var fuseConfig = fuse_config.allocate(arena); fuseImpl.init(connInfo, fuseConfig); - Assertions.assertEquals(FuseConnInfo.FUSE_CAP_READDIRPLUS, result.get() & FuseConnInfo.FUSE_CAP_READDIRPLUS); + Assertions.assertInstanceOf(FuseConnInfoImpl.class, consumerRecievedConnInfo.get()); + Assertions.assertEquals(FuseConnInfo.FUSE_CAP_READDIRPLUS, consumerRecievedConnInfo.get().want() & FuseConnInfo.FUSE_CAP_READDIRPLUS); } } @@ -189,7 +190,13 @@ public void testInit316() { public void testInit317() { try (var fuseFunctionsClass = Mockito.mockStatic(FuseFunctions.class); var arena = Arena.ofConfined()) { + var consumerRecievedConnInfo = new AtomicReference(); + Mockito.doAnswer(invocation -> { + consumerRecievedConnInfo.set(invocation.getArgument(0)); + return null; + }).when(fuseOps).init(Mockito.any(), Mockito.any()); var connInfo = fuse_conn_info.allocate(arena); + fuse_conn_info.proto_minor(connInfo, 17); var fuseConfig = fuse_config.allocate(arena); fuseFunctionsClass.when(() -> FuseFunctions.fuse_set_feature_flag(connInfo, FuseConnInfo.FUSE_CAP_READDIRPLUS)).thenReturn(true); @@ -197,6 +204,7 @@ public void testInit317() { fuseImpl.init(connInfo, fuseConfig); fuseFunctionsClass.verify(() -> FuseFunctions.fuse_set_feature_flag(connInfo, FuseConnInfo.FUSE_CAP_READDIRPLUS)); + Assertions.assertInstanceOf(FuseConnInfoImpl317.class, consumerRecievedConnInfo.get()); } } } From b63962b2e1d584cf646462a6e28a3de7a1249485 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 15 Sep 2025 12:51:04 +0200 Subject: [PATCH 10/16] fix wrong FunctionDescriptor --- .../java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java index b26eeb25..e88e04c3 100644 --- a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java @@ -21,9 +21,9 @@ class FuseFunctions { // see https://github.com/libfuse/libfuse/blob/fuse-3.12.0/include/fuse_lowlevel.h#L1892-L1923 private static final FunctionDescriptor FUSE_PARSE_CMDLINE = FunctionDescriptor.of(JAVA_INT, ADDRESS, ADDRESS); //https://github.com/libfuse/libfuse/blob/fuse-3.17.4/lib/fuse_lowlevel.c#L2035 - private static final FunctionDescriptor FUSE_SET_FEATURE_FLAG = FunctionDescriptor.of(ADDRESS, JAVA_LONG); + private static final FunctionDescriptor FUSE_SET_FEATURE_FLAG = FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_LONG); private static final FunctionDescriptor FUSE_UNSET_FEATURE_FLAG = FunctionDescriptor.of(ADDRESS, JAVA_LONG); - private static final FunctionDescriptor FUSE_GET_FEATURE_FLAG = FunctionDescriptor.of(ADDRESS, JAVA_LONG); + private static final FunctionDescriptor FUSE_GET_FEATURE_FLAG = FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_LONG); private final MethodHandle fuse_parse_cmdline; private final Optional fuse_set_feature_flag; From 309ebf6bdc00a8aa4f18a029f80381dc484dee83 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 15 Sep 2025 12:58:25 +0200 Subject: [PATCH 11/16] use correct version information --- .../org/cryptomator/jfuse/linux/amd64/FuseImpl.java | 2 +- .../cryptomator/jfuse/linux/amd64/FuseImplTest.java | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java index ec57004c..57b49ff8 100644 --- a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java @@ -109,7 +109,7 @@ protected void bind(FuseOperations.Operation operation) { @VisibleForTesting MemorySegment init(MemorySegment conn, MemorySegment cfg) { var connInfo = new FuseConnInfoImpl(conn); - if(connInfo.protoMinor() >= 17 ) { + if(fuse_h.fuse_version() >= 317) { connInfo = new FuseConnInfoImpl317(conn); connInfo.setFeatureFlag(FuseConnInfo.FUSE_CAP_READDIRPLUS); } else { diff --git a/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseImplTest.java b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseImplTest.java index 7ff8ce01..f83cd3fd 100644 --- a/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseImplTest.java +++ b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseImplTest.java @@ -167,17 +167,20 @@ public class Init { @Test public void testInit316() { try (var fuseFunctionsClass = Mockito.mockStatic(FuseFunctions.class); + var fuseH = Mockito.mockStatic(fuse_h.class); var arena = Arena.ofConfined()) { - fuseFunctionsClass.when(() -> FuseFunctions.fuse_set_feature_flag(Mockito.any(), Mockito.anyLong())).thenThrow(UnsupportedOperationException.class); + var consumerRecievedConnInfo = new AtomicReference(); Mockito.doAnswer(invocation -> { consumerRecievedConnInfo.set(invocation.getArgument(0)); return null; }).when(fuseOps).init(Mockito.any(), Mockito.any()); var connInfo = fuse_conn_info.allocate(arena); - fuse_conn_info.proto_minor(connInfo, 16); var fuseConfig = fuse_config.allocate(arena); + fuseH.when(fuse_h::fuse_version).thenReturn(316); + fuseFunctionsClass.when(() -> FuseFunctions.fuse_set_feature_flag(Mockito.any(), Mockito.anyLong())).thenThrow(UnsupportedOperationException.class); + fuseImpl.init(connInfo, fuseConfig); Assertions.assertInstanceOf(FuseConnInfoImpl.class, consumerRecievedConnInfo.get()); @@ -189,18 +192,21 @@ public void testInit316() { @Test public void testInit317() { try (var fuseFunctionsClass = Mockito.mockStatic(FuseFunctions.class); + var fuseH = Mockito.mockStatic(fuse_h.class); var arena = Arena.ofConfined()) { + var consumerRecievedConnInfo = new AtomicReference(); Mockito.doAnswer(invocation -> { consumerRecievedConnInfo.set(invocation.getArgument(0)); return null; }).when(fuseOps).init(Mockito.any(), Mockito.any()); var connInfo = fuse_conn_info.allocate(arena); - fuse_conn_info.proto_minor(connInfo, 17); var fuseConfig = fuse_config.allocate(arena); + fuseH.when(fuse_h::fuse_version).thenReturn(317); fuseFunctionsClass.when(() -> FuseFunctions.fuse_set_feature_flag(connInfo, FuseConnInfo.FUSE_CAP_READDIRPLUS)).thenReturn(true); + fuseImpl.init(connInfo, fuseConfig); fuseFunctionsClass.verify(() -> FuseFunctions.fuse_set_feature_flag(connInfo, FuseConnInfo.FUSE_CAP_READDIRPLUS)); From 3728833e03e7722cea0e15a0817e2092cf285fbf Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 15 Sep 2025 13:17:53 +0200 Subject: [PATCH 12/16] implement feature also for linux aarch64 --- .../jfuse/linux/aarch64/FuseConnInfoImpl.java | 37 +++++---------- .../linux/aarch64/FuseConnInfoImpl317.java | 45 +++++++++++++++++++ .../jfuse/linux/aarch64/FuseFunctions.java | 20 +++++++-- .../jfuse/linux/aarch64/FuseImpl.java | 5 ++- 4 files changed, 76 insertions(+), 31 deletions(-) create mode 100644 jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317.java diff --git a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl.java b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl.java index 20ad18a5..244081ed 100644 --- a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl.java +++ b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl.java @@ -5,7 +5,18 @@ import java.lang.foreign.MemorySegment; -record FuseConnInfoImpl(MemorySegment segment) implements FuseConnInfo { +class FuseConnInfoImpl implements FuseConnInfo { + + protected MemorySegment segment; + + FuseConnInfoImpl(MemorySegment segment) { + this.segment = segment; + } + + //mimic record behaviour + public MemorySegment segment() { + return segment; + } @Override public int protoMajor() { @@ -92,28 +103,4 @@ public void setTimeGran(int timeGran) { fuse_conn_info.time_gran(segment, timeGran); } - @Override - public long capableExt() { - return fuse_conn_info.capable_ext(segment); - } - - @Override - public long wantExt() { - return fuse_conn_info.want_ext(segment); - } - - @Override - public void setWantExt(long wantExt) { - fuse_conn_info.want_ext(segment, wantExt); - } - - @Override - public boolean setFeatureFlag(long flag) throws UnsupportedOperationException { - return FuseFunctions.fuse_set_feature_flag(segment, flag); - } - - @Override - public void unsetFeatureFlag(long flag) throws UnsupportedOperationException { - FuseFunctions.fuse_unset_feature_flag(segment, flag); - } } diff --git a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317.java b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317.java new file mode 100644 index 00000000..cf304a6a --- /dev/null +++ b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317.java @@ -0,0 +1,45 @@ +package org.cryptomator.jfuse.linux.aarch64; + +import org.cryptomator.jfuse.linux.aarch64.extr.fuse3.fuse_conn_info; + +import java.lang.foreign.MemorySegment; + +class FuseConnInfoImpl317 extends FuseConnInfoImpl { + + FuseConnInfoImpl317(MemorySegment segment) { + super(segment); + } + + @Override + public long capableExt() { + //old school style + //return segment.get(fuse_conn_info.capable_ext$layout().withByteAlignment(1), fuse_conn_info.capable_ext$offset()); + return fuse_conn_info.capable_ext(segment); + } + + @Override + public long wantExt() { + return fuse_conn_info.want_ext(segment); + } + + @Override + public void setWantExt(long wantExt) { + fuse_conn_info.want_ext(segment, wantExt); + } + + @Override + public boolean setFeatureFlag(long flag) { + return FuseFunctions.fuse_set_feature_flag(segment, flag); + } + + @Override + public void unsetFeatureFlag(long flag) { + FuseFunctions.fuse_unset_feature_flag(segment, flag); + } + + @Override + public boolean getFeatureFlag(long flag) { + return FuseFunctions.fuse_get_feature_flag(segment, flag); + } + +} diff --git a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseFunctions.java b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseFunctions.java index 59a92d6d..e3e140bb 100644 --- a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseFunctions.java +++ b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseFunctions.java @@ -14,19 +14,21 @@ /** * These method references can not be jextract'ed, partly due to jextract not being able to understand {@code #define}, * partly due to slight differences in the FUSE API, which applies a versioning scheme via dlvsym, that Panama's default - * {@link java.lang.foreign.SymbolLookup} doesn't support. + * {@link SymbolLookup} doesn't support. */ class FuseFunctions { // see https://github.com/libfuse/libfuse/blob/fuse-3.12.0/include/fuse_lowlevel.h#L1892-L1923 private static final FunctionDescriptor FUSE_PARSE_CMDLINE = FunctionDescriptor.of(JAVA_INT, ADDRESS, ADDRESS); //https://github.com/libfuse/libfuse/blob/fuse-3.17.4/lib/fuse_lowlevel.c#L2035 - private static final FunctionDescriptor FUSE_SET_FEATURE_FLAG = FunctionDescriptor.of(ADDRESS, JAVA_LONG); + private static final FunctionDescriptor FUSE_SET_FEATURE_FLAG = FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_LONG); private static final FunctionDescriptor FUSE_UNSET_FEATURE_FLAG = FunctionDescriptor.of(ADDRESS, JAVA_LONG); + private static final FunctionDescriptor FUSE_GET_FEATURE_FLAG = FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_LONG); private final MethodHandle fuse_parse_cmdline; private final Optional fuse_set_feature_flag; private final Optional fuse_unset_feature_flag; + private final Optional fuse_get_feature_flag; private FuseFunctions() { var lookup = SymbolLookup.loaderLookup(); @@ -38,6 +40,8 @@ private FuseFunctions() { .map(symbol -> linker.downcallHandle(symbol, FUSE_SET_FEATURE_FLAG)); this.fuse_unset_feature_flag = lookup.find("fuse_unset_feature_flag") .map(symbol -> linker.downcallHandle(symbol, FUSE_UNSET_FEATURE_FLAG)); + this.fuse_get_feature_flag = lookup.find("fuse_get_feature_flag") + .map(symbol -> linker.downcallHandle(symbol, FUSE_GET_FEATURE_FLAG)); } private static class Holder { @@ -53,7 +57,7 @@ public static int fuse_parse_cmdline(MemorySegment args, MemorySegment opts) { } public static boolean fuse_set_feature_flag(MemorySegment fuse_conn_info, long flag) throws UnsupportedOperationException { - var method = Holder.INSTANCE.fuse_set_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not support fuse_set_feature_flag")); + var method = Holder.INSTANCE.fuse_set_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not implement fuse_set_feature_flag")); try { return ((int) method.invokeExact(fuse_conn_info, flag)) != 0; } catch (Throwable e) { @@ -62,7 +66,7 @@ public static boolean fuse_set_feature_flag(MemorySegment fuse_conn_info, long f } public static void fuse_unset_feature_flag(MemorySegment fuse_conn_info, long flag) throws UnsupportedOperationException { - var method = Holder.INSTANCE.fuse_unset_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not support fuse_unset_feature_flag")); + var method = Holder.INSTANCE.fuse_unset_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not implement fuse_unset_feature_flag")); try { method.invokeExact(fuse_conn_info, flag); } catch (Throwable e) { @@ -70,5 +74,13 @@ public static void fuse_unset_feature_flag(MemorySegment fuse_conn_info, long fl } } + public static boolean fuse_get_feature_flag(MemorySegment fuse_conn_info, long flag) throws UnsupportedOperationException { + var method = Holder.INSTANCE.fuse_get_feature_flag.orElseThrow(() -> new UnsupportedOperationException("The loaded fuse library does not implement fuse_get_feature_flag")); + try { + return ((int) method.invokeExact(fuse_conn_info, flag)) != 0; + } catch (Throwable e) { + throw new AssertionError("should not reach here", e); + } + } } diff --git a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseImpl.java b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseImpl.java index 75b5ee22..f48757e8 100644 --- a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseImpl.java +++ b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseImpl.java @@ -109,9 +109,10 @@ protected void bind(FuseOperations.Operation operation) { @VisibleForTesting MemorySegment init(MemorySegment conn, MemorySegment cfg) { var connInfo = new FuseConnInfoImpl(conn); - try { + if (fuse_h.fuse_version() >= 317) { + connInfo = new FuseConnInfoImpl317(conn); connInfo.setFeatureFlag(FuseConnInfo.FUSE_CAP_READDIRPLUS); - } catch (UnsupportedOperationException _) { + } else { connInfo.setWant(connInfo.want() | FuseConnInfo.FUSE_CAP_READDIRPLUS); } var config = new FuseConfigImpl(cfg); From a51bbf31f1bbd4c67b2b9e823690e99fd49a5244 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 15 Sep 2025 13:20:46 +0200 Subject: [PATCH 13/16] revert 545d432367e558efaff2e7ce4f79870a2a7b055a --- .../linux/aarch64/FuseConnInfoImplTest.java | 169 +++++------------- .../linux/amd64/FuseConnInfoImplTest.java | 17 +- 2 files changed, 53 insertions(+), 133 deletions(-) diff --git a/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImplTest.java b/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImplTest.java index 6aa9eb4a..e0733c39 100644 --- a/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImplTest.java +++ b/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImplTest.java @@ -5,7 +5,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Named; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -18,143 +17,67 @@ public class FuseConnInfoImplTest { - @Nested - public class IntFieldTests { + @DisplayName("test getters") + @ParameterizedTest(name = "{1}") + @MethodSource + public void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { + try (var arena = Arena.ofConfined()) { + var segment = fuse_conn_info.allocate(arena); + var connInfo = new FuseConnInfoImpl(segment); - @DisplayName("test getters") - @ParameterizedTest(name = "{1}") - @MethodSource - void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { - try (var arena = Arena.ofConfined()) { - var segment = fuse_conn_info.allocate(arena); - var connInfo = new FuseConnInfoImpl(segment); + setter.accept(segment, 42); - setter.accept(segment, 42); - - Assertions.assertEquals(42, getter.apply(connInfo)); - } - } - - public static Stream testGetters() { - return Stream.of( - Arguments.arguments((SetInMemorySegment) fuse_conn_info::proto_major, Named.of("protoMajor()", (GetInConnInfo) FuseConnInfo::protoMajor)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::proto_minor, Named.of("protoMinor()", (GetInConnInfo) FuseConnInfo::protoMinor)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable, Named.of("capable()", (GetInConnInfo) FuseConnInfo::capable)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_write, Named.of("maxWrite()", (GetInConnInfo) FuseConnInfo::maxWrite)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_read, Named.of("maxRead()", (GetInConnInfo) FuseConnInfo::maxRead)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_readahead, Named.of("maxReadahead()", (GetInConnInfo) FuseConnInfo::maxReadahead)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_background, Named.of("maxBackground()", (GetInConnInfo) FuseConnInfo::maxBackground)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::congestion_threshold, Named.of("congestionThreshold()", (GetInConnInfo) FuseConnInfo::congestionThreshold)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::time_gran, Named.of("timeGran()", (GetInConnInfo) FuseConnInfo::timeGran)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::want, Named.of("want()", (GetInConnInfo) FuseConnInfo::want)) - ); - } - - interface SetInMemorySegment extends GenericSetInMemorySegment { - } - - interface GetInConnInfo extends GenericGetInConnInfo { - } - - @DisplayName("test setters") - @ParameterizedTest(name = "{0}") - @MethodSource - void testSetters(SetInConnInfo setter, GetInMemorySegment getter) { - try (var arena = Arena.ofConfined()) { - var segment = fuse_conn_info.allocate(arena); - var connInfo = new FuseConnInfoImpl(segment); - - setter.accept(connInfo, 42); - - Assertions.assertEquals(42, getter.apply(segment)); - } - } - - public static Stream testSetters() { - return Stream.of( - Arguments.arguments(Named.of("setWant()", (SetInConnInfo) FuseConnInfo::setWant), (GetInMemorySegment) fuse_conn_info::want), - Arguments.arguments(Named.of("setMaxWrite()", (SetInConnInfo) FuseConnInfo::setMaxWrite), (GetInMemorySegment) fuse_conn_info::max_write), - Arguments.arguments(Named.of("setMaxRead()", (SetInConnInfo) FuseConnInfo::setMaxRead), (GetInMemorySegment) fuse_conn_info::max_read), - Arguments.arguments(Named.of("setMaxReadahead()", (SetInConnInfo) FuseConnInfo::setMaxReadahead), (GetInMemorySegment) fuse_conn_info::max_readahead), - Arguments.arguments(Named.of("setMaxBackground()", (SetInConnInfo) FuseConnInfo::setMaxBackground), (GetInMemorySegment) fuse_conn_info::max_background), - Arguments.arguments(Named.of("setCongestionThreshold()", (SetInConnInfo) FuseConnInfo::setCongestionThreshold), (GetInMemorySegment) fuse_conn_info::congestion_threshold), - Arguments.arguments(Named.of("setTimeGran()", (SetInConnInfo) FuseConnInfo::setTimeGran), (GetInMemorySegment) fuse_conn_info::time_gran) - ); - } - - interface SetInConnInfo extends GenericSetInConnInfo { - } - - interface GetInMemorySegment extends GenericGetInMemorySegment { + Assertions.assertEquals(42, getter.apply(connInfo)); } } - @Nested - public class LongFieldTest { - - @DisplayName("test long getters") - @ParameterizedTest(name = "{1}") - @MethodSource - void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { - try (var arena = Arena.ofConfined()) { - var segment = fuse_conn_info.allocate(arena); - var connInfo = new FuseConnInfoImpl(segment); - - setter.accept(segment, 42L); - - Assertions.assertEquals(42L, getter.apply(connInfo)); - } - } - - public static Stream testGetters() { - return Stream.of( - Arguments.arguments((SetInMemorySegment) fuse_conn_info::want_ext, Named.of("wantExt()", (GetInConnInfo) FuseConnInfo::wantExt)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable_ext, Named.of("capable()", (GetInConnInfo) FuseConnInfo::capableExt)) - ); - } - - interface SetInMemorySegment extends GenericSetInMemorySegment { - } - - interface GetInConnInfo extends GenericGetInConnInfo { - } - - @DisplayName("test setters") - @ParameterizedTest(name = "{0}") - @MethodSource - void testSetters(SetInConnInfo setter, GetInMemorySegment getter) { - try (var arena = Arena.ofConfined()) { - var segment = fuse_conn_info.allocate(arena); - var connInfo = new FuseConnInfoImpl(segment); + public static Stream testGetters() { + return Stream.of( + Arguments.arguments((SetInMemorySegment) fuse_conn_info::proto_major, Named.of("protoMajor()", (GetInConnInfo) FuseConnInfo::protoMajor)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::proto_minor, Named.of("protoMinor()", (GetInConnInfo) FuseConnInfo::protoMinor)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable, Named.of("capable()", (GetInConnInfo) FuseConnInfo::capable)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_write, Named.of("maxWrite()", (GetInConnInfo) FuseConnInfo::maxWrite)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_read, Named.of("maxRead()", (GetInConnInfo) FuseConnInfo::maxRead)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_readahead, Named.of("maxReadahead()", (GetInConnInfo) FuseConnInfo::maxReadahead)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::max_background, Named.of("maxBackground()", (GetInConnInfo) FuseConnInfo::maxBackground)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::congestion_threshold, Named.of("congestionThreshold()", (GetInConnInfo) FuseConnInfo::congestionThreshold)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::time_gran, Named.of("timeGran()", (GetInConnInfo) FuseConnInfo::timeGran)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::want, Named.of("want()", (GetInConnInfo) FuseConnInfo::want)) + ); + } - setter.accept(connInfo, 42L); + private interface SetInMemorySegment extends BiConsumer {} - Assertions.assertEquals(42L, getter.apply(segment)); - } - } + private interface GetInConnInfo extends Function {} - public static Stream testSetters() { - return Stream.of( - Arguments.arguments(Named.of("setWantExt()", (SetInConnInfo) FuseConnInfo::setWantExt), (GetInMemorySegment) fuse_conn_info::want_ext) - ); - } + @DisplayName("test setters") + @ParameterizedTest(name = "{0}") + @MethodSource + public void testSetters(SetInConnInfo setter, GetInMemorySegment getter) { + try (var arena = Arena.ofConfined()) { + var segment = fuse_conn_info.allocate(arena); + var connInfo = new FuseConnInfoImpl(segment); - interface SetInConnInfo extends GenericSetInConnInfo { - } + setter.accept(connInfo, 42); - interface GetInMemorySegment extends GenericGetInMemorySegment { + Assertions.assertEquals(42, getter.apply(segment)); } } - private interface GenericSetInMemorySegment extends BiConsumer { + public static Stream testSetters() { + return Stream.of( + Arguments.arguments(Named.of("setWant()", (SetInConnInfo) FuseConnInfo::setWant), (GetInMemorySegment) fuse_conn_info::want), + Arguments.arguments(Named.of("setMaxWrite()", (SetInConnInfo) FuseConnInfo::setMaxWrite), (GetInMemorySegment) fuse_conn_info::max_write), + Arguments.arguments(Named.of("setMaxRead()", (SetInConnInfo) FuseConnInfo::setMaxRead), (GetInMemorySegment) fuse_conn_info::max_read), + Arguments.arguments(Named.of("setMaxReadahead()", (SetInConnInfo) FuseConnInfo::setMaxReadahead), (GetInMemorySegment) fuse_conn_info::max_readahead), + Arguments.arguments(Named.of("setMaxBackground()", (SetInConnInfo) FuseConnInfo::setMaxBackground), (GetInMemorySegment) fuse_conn_info::max_background), + Arguments.arguments(Named.of("setCongestionThreshold()", (SetInConnInfo) FuseConnInfo::setCongestionThreshold), (GetInMemorySegment) fuse_conn_info::congestion_threshold), + Arguments.arguments(Named.of("setTimeGran()", (SetInConnInfo) FuseConnInfo::setTimeGran), (GetInMemorySegment) fuse_conn_info::time_gran) + ); } - private interface GenericGetInConnInfo extends Function { - } + private interface SetInConnInfo extends BiConsumer {} - private interface GenericSetInConnInfo extends BiConsumer { - } + private interface GetInMemorySegment extends Function {} - private interface GenericGetInMemorySegment extends Function { - } } \ No newline at end of file diff --git a/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImplTest.java b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImplTest.java index 36b789d9..6165d960 100644 --- a/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImplTest.java +++ b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImplTest.java @@ -20,7 +20,7 @@ public class FuseConnInfoImplTest { @DisplayName("test getters") @ParameterizedTest(name = "{1}") @MethodSource - void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { + public void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { try (var arena = Arena.ofConfined()) { var segment = fuse_conn_info.allocate(arena); var connInfo = new FuseConnInfoImpl(segment); @@ -46,16 +46,14 @@ public static Stream testGetters() { ); } - interface SetInMemorySegment extends BiConsumer { - } + private interface SetInMemorySegment extends BiConsumer {} - interface GetInConnInfo extends Function { - } + private interface GetInConnInfo extends Function {} @DisplayName("test setters") @ParameterizedTest(name = "{0}") @MethodSource - void testSetters(SetInConnInfo setter, GetInMemorySegment getter) { + public void testSetters(SetInConnInfo setter, GetInMemorySegment getter) { try (var arena = Arena.ofConfined()) { var segment = fuse_conn_info.allocate(arena); var connInfo = new FuseConnInfoImpl(segment); @@ -78,9 +76,8 @@ public static Stream testSetters() { ); } - interface SetInConnInfo extends BiConsumer { - } + private interface SetInConnInfo extends BiConsumer {} + + private interface GetInMemorySegment extends Function {} - interface GetInMemorySegment extends Function { - } } \ No newline at end of file From f3624083c81fd3cef8991c9cadda673c1e249c02 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 15 Sep 2025 13:22:38 +0200 Subject: [PATCH 14/16] add missing unit tests --- .../aarch64/FuseConnInfoImpl317Test.java | 72 +++++++++++++++++++ .../jfuse/linux/aarch64/FuseImplTest.java | 26 +++++-- .../jfuse/linux/amd64/FuseImpl.java | 2 +- 3 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317Test.java diff --git a/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317Test.java b/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317Test.java new file mode 100644 index 00000000..7012aced --- /dev/null +++ b/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317Test.java @@ -0,0 +1,72 @@ +package org.cryptomator.jfuse.linux.aarch64; + +import org.cryptomator.jfuse.api.FuseConnInfo; +import org.cryptomator.jfuse.linux.aarch64.extr.fuse3.fuse_conn_info; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.stream.Stream; + +public class FuseConnInfoImpl317Test { + + @DisplayName("test long getters") + @ParameterizedTest(name = "{1}") + @MethodSource + void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { + try (var arena = Arena.ofConfined()) { + var segment = fuse_conn_info.allocate(arena); + var connInfo = new FuseConnInfoImpl317(segment); + + setter.accept(segment, 42L); + + Assertions.assertEquals(42L, getter.apply(connInfo)); + } + } + + public static Stream testGetters() { + return Stream.of( + Arguments.arguments((SetInMemorySegment) fuse_conn_info::want_ext, Named.of("wantExt()", (GetInConnInfo) FuseConnInfo::wantExt)), + Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable_ext, Named.of("capable()", (GetInConnInfo) FuseConnInfo::capableExt)) + ); + } + + interface SetInMemorySegment extends BiConsumer { + } + + interface GetInConnInfo extends Function { + } + + @DisplayName("test setters") + @ParameterizedTest(name = "{0}") + @MethodSource + void testSetters(SetInConnInfo setter, GetInMemorySegment getter) { + try (var arena = Arena.ofConfined()) { + var segment = fuse_conn_info.allocate(arena); + var connInfo = new FuseConnInfoImpl317(segment); + + setter.accept(connInfo, 42L); + + Assertions.assertEquals(42L, getter.apply(segment)); + } + } + + public static Stream testSetters() { + return Stream.of( + Arguments.arguments(Named.of("setWantExt()", (SetInConnInfo) FuseConnInfo::setWantExt), (GetInMemorySegment) fuse_conn_info::want_ext) + ); + } + + interface SetInConnInfo extends BiConsumer { + } + + interface GetInMemorySegment extends Function { + } +} diff --git a/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseImplTest.java b/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseImplTest.java index b1e424d4..5ef73992 100644 --- a/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseImplTest.java +++ b/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseImplTest.java @@ -24,7 +24,7 @@ import java.lang.foreign.MemorySegment; import java.time.Instant; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; public class FuseImplTest { @@ -167,20 +167,24 @@ public class Init { @Test public void testInit316() { try (var fuseFunctionsClass = Mockito.mockStatic(FuseFunctions.class); + var fuseH = Mockito.mockStatic(fuse_h.class); var arena = Arena.ofConfined()) { - fuseFunctionsClass.when(() -> FuseFunctions.fuse_set_feature_flag(Mockito.any(), Mockito.anyLong())).thenThrow(UnsupportedOperationException.class); - var result = new AtomicInteger(); + + var consumerRecievedConnInfo = new AtomicReference(); Mockito.doAnswer(invocation -> { - FuseConnInfo connInfo = invocation.getArgument(0); - result.set(connInfo.want()); + consumerRecievedConnInfo.set(invocation.getArgument(0)); return null; }).when(fuseOps).init(Mockito.any(), Mockito.any()); var connInfo = fuse_conn_info.allocate(arena); var fuseConfig = fuse_config.allocate(arena); + fuseH.when(fuse_h::fuse_version).thenReturn(316); + fuseFunctionsClass.when(() -> FuseFunctions.fuse_set_feature_flag(Mockito.any(), Mockito.anyLong())).thenThrow(UnsupportedOperationException.class); + fuseImpl.init(connInfo, fuseConfig); - Assertions.assertEquals(FuseConnInfo.FUSE_CAP_READDIRPLUS, result.get() & FuseConnInfo.FUSE_CAP_READDIRPLUS); + Assertions.assertInstanceOf(FuseConnInfoImpl.class, consumerRecievedConnInfo.get()); + Assertions.assertEquals(FuseConnInfo.FUSE_CAP_READDIRPLUS, consumerRecievedConnInfo.get().want() & FuseConnInfo.FUSE_CAP_READDIRPLUS); } } @@ -188,15 +192,25 @@ public void testInit316() { @Test public void testInit317() { try (var fuseFunctionsClass = Mockito.mockStatic(FuseFunctions.class); + var fuseH = Mockito.mockStatic(fuse_h.class); var arena = Arena.ofConfined()) { + + var consumerRecievedConnInfo = new AtomicReference(); + Mockito.doAnswer(invocation -> { + consumerRecievedConnInfo.set(invocation.getArgument(0)); + return null; + }).when(fuseOps).init(Mockito.any(), Mockito.any()); var connInfo = fuse_conn_info.allocate(arena); var fuseConfig = fuse_config.allocate(arena); + fuseH.when(fuse_h::fuse_version).thenReturn(317); fuseFunctionsClass.when(() -> FuseFunctions.fuse_set_feature_flag(connInfo, FuseConnInfo.FUSE_CAP_READDIRPLUS)).thenReturn(true); + fuseImpl.init(connInfo, fuseConfig); fuseFunctionsClass.verify(() -> FuseFunctions.fuse_set_feature_flag(connInfo, FuseConnInfo.FUSE_CAP_READDIRPLUS)); + Assertions.assertInstanceOf(FuseConnInfoImpl317.class, consumerRecievedConnInfo.get()); } } } diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java index 57b49ff8..f62038b0 100644 --- a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseImpl.java @@ -109,7 +109,7 @@ protected void bind(FuseOperations.Operation operation) { @VisibleForTesting MemorySegment init(MemorySegment conn, MemorySegment cfg) { var connInfo = new FuseConnInfoImpl(conn); - if(fuse_h.fuse_version() >= 317) { + if (fuse_h.fuse_version() >= 317) { connInfo = new FuseConnInfoImpl317(conn); connInfo.setFeatureFlag(FuseConnInfo.FUSE_CAP_READDIRPLUS); } else { From 5fe0be77b03a12dd4de0f68af49e590de3318f94 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 15 Sep 2025 13:37:35 +0200 Subject: [PATCH 15/16] Fix wrong function descriptors --- .../java/org/cryptomator/jfuse/linux/aarch64/FuseFunctions.java | 2 +- .../java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseFunctions.java b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseFunctions.java index e3e140bb..ce58a7a3 100644 --- a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseFunctions.java +++ b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseFunctions.java @@ -22,7 +22,7 @@ class FuseFunctions { private static final FunctionDescriptor FUSE_PARSE_CMDLINE = FunctionDescriptor.of(JAVA_INT, ADDRESS, ADDRESS); //https://github.com/libfuse/libfuse/blob/fuse-3.17.4/lib/fuse_lowlevel.c#L2035 private static final FunctionDescriptor FUSE_SET_FEATURE_FLAG = FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_LONG); - private static final FunctionDescriptor FUSE_UNSET_FEATURE_FLAG = FunctionDescriptor.of(ADDRESS, JAVA_LONG); + private static final FunctionDescriptor FUSE_UNSET_FEATURE_FLAG = FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG); private static final FunctionDescriptor FUSE_GET_FEATURE_FLAG = FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_LONG); private final MethodHandle fuse_parse_cmdline; diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java index e88e04c3..490a131b 100644 --- a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java @@ -22,7 +22,7 @@ class FuseFunctions { private static final FunctionDescriptor FUSE_PARSE_CMDLINE = FunctionDescriptor.of(JAVA_INT, ADDRESS, ADDRESS); //https://github.com/libfuse/libfuse/blob/fuse-3.17.4/lib/fuse_lowlevel.c#L2035 private static final FunctionDescriptor FUSE_SET_FEATURE_FLAG = FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_LONG); - private static final FunctionDescriptor FUSE_UNSET_FEATURE_FLAG = FunctionDescriptor.of(ADDRESS, JAVA_LONG); + private static final FunctionDescriptor FUSE_UNSET_FEATURE_FLAG = FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG); private static final FunctionDescriptor FUSE_GET_FEATURE_FLAG = FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_LONG); private final MethodHandle fuse_parse_cmdline; From 7327efd876e184c42b4a21625373f3a45065d0b7 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 15 Sep 2025 13:48:47 +0200 Subject: [PATCH 16/16] minor review findings --- .../org/cryptomator/jfuse/api/FuseConnInfo.java | 3 ++- .../jfuse/linux/aarch64/FuseConnInfoImpl.java | 2 +- .../jfuse/linux/aarch64/FuseConnInfoImpl317.java | 2 -- .../linux/aarch64/FuseConnInfoImpl317Test.java | 2 +- .../jfuse/linux/aarch64/FuseImplTest.java | 14 +++++++------- .../jfuse/linux/amd64/FuseConnInfoImpl.java | 2 +- .../jfuse/linux/amd64/FuseConnInfoImpl317Test.java | 2 +- .../jfuse/linux/amd64/FuseImplTest.java | 14 +++++++------- 8 files changed, 20 insertions(+), 21 deletions(-) diff --git a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseConnInfo.java b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseConnInfo.java index 2205d2e8..90765b3b 100644 --- a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseConnInfo.java +++ b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseConnInfo.java @@ -539,6 +539,7 @@ default long wantExt() { /** * Sets the {@link #wantExt()} value. *

+ * Deprecated way of setting the wantExt field. Use {@link #setFeatureFlag(long)} instead. * If the version of the loaded FUSE library is below 3.17, this method does nothing. * * @param wantExt {@code want_ext} value @@ -584,6 +585,6 @@ default void unsetFeatureFlag(long flag) throws UnsupportedOperationException { * @since libFUSE 3.17 */ default boolean getFeatureFlag(long flag) throws UnsupportedOperationException { - throw new UnsupportedOperationException("Loaded library does not implement fuse_unset_feature_flag"); + throw new UnsupportedOperationException("Loaded library does not implement fuse_get_feature_flag"); } } diff --git a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl.java b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl.java index 244081ed..e0bff7c2 100644 --- a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl.java +++ b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl.java @@ -7,7 +7,7 @@ class FuseConnInfoImpl implements FuseConnInfo { - protected MemorySegment segment; + protected final MemorySegment segment; FuseConnInfoImpl(MemorySegment segment) { this.segment = segment; diff --git a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317.java b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317.java index cf304a6a..ce497b74 100644 --- a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317.java +++ b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317.java @@ -12,8 +12,6 @@ class FuseConnInfoImpl317 extends FuseConnInfoImpl { @Override public long capableExt() { - //old school style - //return segment.get(fuse_conn_info.capable_ext$layout().withByteAlignment(1), fuse_conn_info.capable_ext$offset()); return fuse_conn_info.capable_ext(segment); } diff --git a/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317Test.java b/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317Test.java index 7012aced..fff8cc55 100644 --- a/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317Test.java +++ b/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317Test.java @@ -34,7 +34,7 @@ void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { public static Stream testGetters() { return Stream.of( Arguments.arguments((SetInMemorySegment) fuse_conn_info::want_ext, Named.of("wantExt()", (GetInConnInfo) FuseConnInfo::wantExt)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable_ext, Named.of("capable()", (GetInConnInfo) FuseConnInfo::capableExt)) + Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable_ext, Named.of("capableExt()", (GetInConnInfo) FuseConnInfo::capableExt)) ); } diff --git a/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseImplTest.java b/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseImplTest.java index 5ef73992..2235b1a4 100644 --- a/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseImplTest.java +++ b/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseImplTest.java @@ -170,9 +170,9 @@ public void testInit316() { var fuseH = Mockito.mockStatic(fuse_h.class); var arena = Arena.ofConfined()) { - var consumerRecievedConnInfo = new AtomicReference(); + var consumerReceivedConnInfo = new AtomicReference(); Mockito.doAnswer(invocation -> { - consumerRecievedConnInfo.set(invocation.getArgument(0)); + consumerReceivedConnInfo.set(invocation.getArgument(0)); return null; }).when(fuseOps).init(Mockito.any(), Mockito.any()); var connInfo = fuse_conn_info.allocate(arena); @@ -183,8 +183,8 @@ public void testInit316() { fuseImpl.init(connInfo, fuseConfig); - Assertions.assertInstanceOf(FuseConnInfoImpl.class, consumerRecievedConnInfo.get()); - Assertions.assertEquals(FuseConnInfo.FUSE_CAP_READDIRPLUS, consumerRecievedConnInfo.get().want() & FuseConnInfo.FUSE_CAP_READDIRPLUS); + Assertions.assertInstanceOf(FuseConnInfoImpl.class, consumerReceivedConnInfo.get()); + Assertions.assertEquals(FuseConnInfo.FUSE_CAP_READDIRPLUS, consumerReceivedConnInfo.get().want() & FuseConnInfo.FUSE_CAP_READDIRPLUS); } } @@ -195,9 +195,9 @@ public void testInit317() { var fuseH = Mockito.mockStatic(fuse_h.class); var arena = Arena.ofConfined()) { - var consumerRecievedConnInfo = new AtomicReference(); + var consumerReceivedConnInfo = new AtomicReference(); Mockito.doAnswer(invocation -> { - consumerRecievedConnInfo.set(invocation.getArgument(0)); + consumerReceivedConnInfo.set(invocation.getArgument(0)); return null; }).when(fuseOps).init(Mockito.any(), Mockito.any()); var connInfo = fuse_conn_info.allocate(arena); @@ -210,7 +210,7 @@ public void testInit317() { fuseImpl.init(connInfo, fuseConfig); fuseFunctionsClass.verify(() -> FuseFunctions.fuse_set_feature_flag(connInfo, FuseConnInfo.FUSE_CAP_READDIRPLUS)); - Assertions.assertInstanceOf(FuseConnInfoImpl317.class, consumerRecievedConnInfo.get()); + Assertions.assertInstanceOf(FuseConnInfoImpl317.class, consumerReceivedConnInfo.get()); } } } diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl.java index 60192570..071e6761 100644 --- a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl.java +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl.java @@ -7,7 +7,7 @@ class FuseConnInfoImpl implements FuseConnInfo { - protected MemorySegment segment; + protected final MemorySegment segment; FuseConnInfoImpl(MemorySegment segment) { this.segment = segment; diff --git a/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl317Test.java b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl317Test.java index 2ae46d43..134b73f9 100644 --- a/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl317Test.java +++ b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl317Test.java @@ -34,7 +34,7 @@ void testGetters(SetInMemorySegment setter, GetInConnInfo getter) { public static Stream testGetters() { return Stream.of( Arguments.arguments((SetInMemorySegment) fuse_conn_info::want_ext, Named.of("wantExt()", (GetInConnInfo) FuseConnInfo::wantExt)), - Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable_ext, Named.of("capable()", (GetInConnInfo) FuseConnInfo::capableExt)) + Arguments.arguments((SetInMemorySegment) fuse_conn_info::capable_ext, Named.of("capableExt()", (GetInConnInfo) FuseConnInfo::capableExt)) ); } diff --git a/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseImplTest.java b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseImplTest.java index f83cd3fd..32afa877 100644 --- a/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseImplTest.java +++ b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseImplTest.java @@ -170,9 +170,9 @@ public void testInit316() { var fuseH = Mockito.mockStatic(fuse_h.class); var arena = Arena.ofConfined()) { - var consumerRecievedConnInfo = new AtomicReference(); + var consumerReceivedConnInfo = new AtomicReference(); Mockito.doAnswer(invocation -> { - consumerRecievedConnInfo.set(invocation.getArgument(0)); + consumerReceivedConnInfo.set(invocation.getArgument(0)); return null; }).when(fuseOps).init(Mockito.any(), Mockito.any()); var connInfo = fuse_conn_info.allocate(arena); @@ -183,8 +183,8 @@ public void testInit316() { fuseImpl.init(connInfo, fuseConfig); - Assertions.assertInstanceOf(FuseConnInfoImpl.class, consumerRecievedConnInfo.get()); - Assertions.assertEquals(FuseConnInfo.FUSE_CAP_READDIRPLUS, consumerRecievedConnInfo.get().want() & FuseConnInfo.FUSE_CAP_READDIRPLUS); + Assertions.assertInstanceOf(FuseConnInfoImpl.class, consumerReceivedConnInfo.get()); + Assertions.assertEquals(FuseConnInfo.FUSE_CAP_READDIRPLUS, consumerReceivedConnInfo.get().want() & FuseConnInfo.FUSE_CAP_READDIRPLUS); } } @@ -195,9 +195,9 @@ public void testInit317() { var fuseH = Mockito.mockStatic(fuse_h.class); var arena = Arena.ofConfined()) { - var consumerRecievedConnInfo = new AtomicReference(); + var consumerReceivedConnInfo = new AtomicReference(); Mockito.doAnswer(invocation -> { - consumerRecievedConnInfo.set(invocation.getArgument(0)); + consumerReceivedConnInfo.set(invocation.getArgument(0)); return null; }).when(fuseOps).init(Mockito.any(), Mockito.any()); var connInfo = fuse_conn_info.allocate(arena); @@ -210,7 +210,7 @@ public void testInit317() { fuseImpl.init(connInfo, fuseConfig); fuseFunctionsClass.verify(() -> FuseFunctions.fuse_set_feature_flag(connInfo, FuseConnInfo.FUSE_CAP_READDIRPLUS)); - Assertions.assertInstanceOf(FuseConnInfoImpl317.class, consumerRecievedConnInfo.get()); + Assertions.assertInstanceOf(FuseConnInfoImpl317.class, consumerReceivedConnInfo.get()); } } }