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..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
@@ -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,87 @@ 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.
+ *
+ * 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() {
+ 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()
+ *
+ * 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
+ */
+ default long wantExt() {
+ return 0;
+ }
+
+ /**
+ * 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
+ * @implSpec The default implementation is a no-op.
+ * @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 {@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 throws {@link UnsupportedOperationException}
+ * @since libFUSE 3.17
+ */
+ default boolean setFeatureFlag(long flag) throws UnsupportedOperationException {
+ 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.
+ *
+ * @param flag feature flag to be unset
+ * @throws UnsupportedOperationException if the loaded fuse library does not implement the method
+ * @implSpec The default implementation throws {@link UnsupportedOperationException}
+ * @since libFUSE 3.17
+ */
+ default void unsetFeatureFlag(long flag) throws UnsupportedOperationException {
+ 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_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 8b20f86d..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
@@ -3,10 +3,20 @@
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 {
+class FuseConnInfoImpl implements FuseConnInfo {
+
+ protected final MemorySegment segment;
+
+ FuseConnInfoImpl(MemorySegment segment) {
+ this.segment = segment;
+ }
+
+ //mimic record behaviour
+ public MemorySegment segment() {
+ return segment;
+ }
@Override
public int protoMajor() {
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..ce497b74
--- /dev/null
+++ b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317.java
@@ -0,0 +1,43 @@
+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() {
+ 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 52cada82..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
@@ -5,21 +5,30 @@
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},
* 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(JAVA_INT, 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;
+ 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();
@@ -27,6 +36,12 @@ 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));
+ this.fuse_get_feature_flag = lookup.find("fuse_get_feature_flag")
+ .map(symbol -> linker.downcallHandle(symbol, FUSE_GET_FEATURE_FLAG));
}
private static class Holder {
@@ -41,4 +56,31 @@ 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 {
+ 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 {
+ 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) {
+ throw new AssertionError("should not reach here", e);
+ }
+ }
+
+ 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 e529f481..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,7 +109,12 @@ 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);
+ if (fuse_h.fuse_version() >= 317) {
+ connInfo = new FuseConnInfoImpl317(conn);
+ connInfo.setFeatureFlag(FuseConnInfo.FUSE_CAP_READDIRPLUS);
+ } else {
+ 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/FuseConnInfoImpl317Test.java b/jfuse-linux-aarch64/src/test/java/org/cryptomator/jfuse/linux/aarch64/FuseConnInfoImpl317Test.java
new file mode 100644
index 00000000..fff8cc55
--- /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("capableExt()", (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 626d26a8..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
@@ -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,14 +18,13 @@
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;
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 {
@@ -162,22 +159,59 @@ 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 fuseH = Mockito.mockStatic(fuse_h.class);
+ var arena = Arena.ofConfined()) {
+
+ var consumerReceivedConnInfo = new AtomicReference();
+ Mockito.doAnswer(invocation -> {
+ consumerReceivedConnInfo.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.assertInstanceOf(FuseConnInfoImpl.class, consumerReceivedConnInfo.get());
+ Assertions.assertEquals(FuseConnInfo.FUSE_CAP_READDIRPLUS, consumerReceivedConnInfo.get().want() & 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 fuseH = Mockito.mockStatic(fuse_h.class);
+ var arena = Arena.ofConfined()) {
+
+ var consumerReceivedConnInfo = new AtomicReference();
+ Mockito.doAnswer(invocation -> {
+ consumerReceivedConnInfo.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, 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 487e0c44..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
@@ -3,10 +3,20 @@
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 {
+class FuseConnInfoImpl implements FuseConnInfo {
+
+ protected final MemorySegment segment;
+
+ FuseConnInfoImpl(MemorySegment segment) {
+ this.segment = segment;
+ }
+
+ //mimic record behaviour
+ public MemorySegment segment() {
+ return segment;
+ }
@Override
public int protoMajor() {
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/FuseFunctions.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FuseFunctions.java
index 4a182345..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
@@ -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,15 @@ 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(JAVA_INT, 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;
+ 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();
@@ -27,6 +36,12 @@ 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));
+ this.fuse_get_feature_flag = lookup.find("fuse_get_feature_flag")
+ .map(symbol -> linker.downcallHandle(symbol, FUSE_GET_FEATURE_FLAG));
}
private static class Holder {
@@ -41,4 +56,31 @@ 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 {
+ 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 {
+ 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) {
+ throw new AssertionError("should not reach here", e);
+ }
+ }
+
+ 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-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..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,12 @@ 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);
+ if (fuse_h.fuse_version() >= 317) {
+ connInfo = new FuseConnInfoImpl317(conn);
+ connInfo.setFeatureFlag(FuseConnInfo.FUSE_CAP_READDIRPLUS);
+ } else {
+ 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/FuseConnInfoImpl317Test.java b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseConnInfoImpl317Test.java
new file mode 100644
index 00000000..134b73f9
--- /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("capableExt()", (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/FuseImplTest.java b/jfuse-linux-amd64/src/test/java/org/cryptomator/jfuse/linux/amd64/FuseImplTest.java
index d93b71b3..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
@@ -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 {
@@ -159,22 +159,59 @@ 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 fuseH = Mockito.mockStatic(fuse_h.class);
+ var arena = Arena.ofConfined()) {
+
+ var consumerReceivedConnInfo = new AtomicReference();
+ Mockito.doAnswer(invocation -> {
+ consumerReceivedConnInfo.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.assertInstanceOf(FuseConnInfoImpl.class, consumerReceivedConnInfo.get());
+ Assertions.assertEquals(FuseConnInfo.FUSE_CAP_READDIRPLUS, consumerReceivedConnInfo.get().want() & 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 fuseH = Mockito.mockStatic(fuse_h.class);
+ var arena = Arena.ofConfined()) {
+
+ var consumerReceivedConnInfo = new AtomicReference();
+ Mockito.doAnswer(invocation -> {
+ consumerReceivedConnInfo.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, consumerReceivedConnInfo.get());
+ }
}
}
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;
diff --git a/pom.xml b/pom.xml
index 681d508d..a73653b2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -119,6 +119,25 @@
org.apache.maven.plugins
maven-javadoc-plugin
3.11.2
+
+
+
+ apiNote
+ a
+ API Note:
+
+
+ implSpec
+ a
+ Implementation Requirements:
+
+
+ implNote
+ a
+ Implementation Note:
+
+
+
org.apache.maven.plugins