From 465e81a4122766433007dec9192d649bed46cfe5 Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven Date: Sun, 26 Apr 2026 16:43:29 -0500 Subject: [PATCH 1/3] fix macFUSE 5.2.0 FSKit compatibility - Move fuse_mount into FuseMountImpl.loop() so mount+loop run on the same thread (required by macFUSE FSKit backend) - Add ensureVolname() to prevent strdup(NULL) crash in fuse_darwin_mount - Add statx field to fuse_operations (47 fields / 376 bytes for 5.2.0) - Call _fuse_new_31 (5-arg) with libfuse_version struct when available - Prefer fuse_parse_cmdline_312 symbol over fuse_parse_cmdline - Only request FUSE_CAP_READDIRPLUS when kernel advertises support - Make PosixMirrorFileSystem backend configurable via -Dfuse.backend - Update tests for deferred fuse_mount and READDIRPLUS capability check Co-Authored-By: Claude Opus 4.6 --- .../jfuse/examples/PosixMirrorFileSystem.java | 7 ++- .../cryptomator/jfuse/mac/FuseFunctions.java | 4 +- .../org/cryptomator/jfuse/mac/FuseImpl.java | 33 +++++++--- .../cryptomator/jfuse/mac/FuseMountImpl.java | 9 +-- .../cryptomator/jfuse/mac/FuseNewHelper.java | 60 ++++++++++++++++--- .../jfuse/mac/extr/fuse3/fuse_operations.java | 3 +- .../cryptomator/jfuse/mac/FuseImplTest.java | 16 ++--- .../cryptomator/jfuse/mac/StatImplTest.java | 2 +- .../cryptomator/jfuse/mac/StatfsImplTest.java | 7 +-- 9 files changed, 104 insertions(+), 37 deletions(-) diff --git a/jfuse-examples/src/main/java/org/cryptomator/jfuse/examples/PosixMirrorFileSystem.java b/jfuse-examples/src/main/java/org/cryptomator/jfuse/examples/PosixMirrorFileSystem.java index 4d084dd1..97374c8d 100644 --- a/jfuse-examples/src/main/java/org/cryptomator/jfuse/examples/PosixMirrorFileSystem.java +++ b/jfuse-examples/src/main/java/org/cryptomator/jfuse/examples/PosixMirrorFileSystem.java @@ -37,7 +37,12 @@ public static void main(String[] args) { } try (var fuse = builder.build(new PosixMirrorFileSystem(mirrored, builder.errno()))) { LOG.info("Mounting at {}...", mountPoint); - fuse.mount("jfuse", mountPoint, "-s", "-obackend=fskit"); + var backend = System.getProperty("fuse.backend", ""); + if (backend.isEmpty()) { + fuse.mount("jfuse", mountPoint, "-s"); + } else { + fuse.mount("jfuse", mountPoint, "-s", "-obackend=" + backend); + } LOG.info("Mounted to {}.", mountPoint); LOG.info("Enter a anything to unmount..."); System.in.read(); diff --git a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseFunctions.java b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseFunctions.java index c28965d9..992c53a7 100644 --- a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseFunctions.java +++ b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseFunctions.java @@ -33,7 +33,9 @@ class FuseFunctions { private FuseFunctions() { var lookup = SymbolLookup.loaderLookup(); var linker = Linker.nativeLinker(); - this.fuse_parse_cmdline = lookup.find("fuse_parse_cmdline") + // Prefer fuse_parse_cmdline_312 (macFUSE 5.2.0+), fall back to fuse_parse_cmdline + this.fuse_parse_cmdline = lookup.find("fuse_parse_cmdline_312") + .or(() -> lookup.find("fuse_parse_cmdline")) .map(symbol -> linker.downcallHandle(symbol, PARSE_CMDLINE_DESCRIPTOR)) .orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol fuse_parse_cmdline")); this.fuse_set_feature_flag = lookup.find("fuse_set_feature_flag") diff --git a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseImpl.java b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseImpl.java index 9186561a..368b179b 100644 --- a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseImpl.java +++ b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseImpl.java @@ -17,6 +17,7 @@ import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; +import java.util.ArrayList; import java.util.List; final class FuseImpl extends Fuse { @@ -27,14 +28,26 @@ public FuseImpl(FuseOperations fuseOperations) { @Override protected FuseMount mount(List args) throws FuseMountFailedException { - var fuseArgs = parseArgs(args); + // macFUSE 5.2.0 crashes in fuse_darwin_mount if volname is not set (strdup of NULL) + var effectiveArgs = ensureVolname(args); + var fuseArgs = parseArgs(effectiveArgs); var fuse = createFuseFS(fuseArgs); - if (fuse_h.fuse_mount(fuse, fuseArgs.mountPoint()) != 0) { - throw new FuseMountFailedException("fuse_mount failed"); - } + // Defer fuse_mount to loop() — macFUSE FSKit requires mount+loop on the same thread return new FuseMountImpl(fuse, fuseArgs); } + private static List ensureVolname(List args) { + for (var arg : args) { + if (arg.contains("volname=")) { + return args; + } + } + var result = new ArrayList<>(args); + var progName = args.isEmpty() ? "jfuse" : args.getFirst(); + result.add("-ovolname=" + progName); + return result; + } + @VisibleForTesting MemorySegment createFuseFS(FuseArgs fuseArgs) throws FuseMountFailedException { var fuse = FuseNewHelper.getInstance().fuse_new(fuseArgs.args(), fuseOperationsStruct, fuseOperationsStruct.byteSize(), MemorySegment.NULL); @@ -46,7 +59,6 @@ MemorySegment createFuseFS(FuseArgs fuseArgs) throws FuseMountFailedException { @VisibleForTesting FuseArgs parseArgs(List cmdLineArgs) throws IllegalArgumentException { - System.out.println("DEBUG: cmdLineArgs = " + cmdLineArgs); // Add this var args = fuse_args.allocate(fuseArena); var argc = cmdLineArgs.size(); var argv = fuseArena.allocate(ValueLayout.ADDRESS, argc + 1L); @@ -112,9 +124,14 @@ MemorySegment init(MemorySegment conn, MemorySegment cfg) { var connInfo = new FuseConnInfoImpl(conn); 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); + } + // only request READDIRPLUS if the kernel advertises support + if ((connInfo.capable() & FuseConnInfo.FUSE_CAP_READDIRPLUS) != 0) { + if (fuse_h.fuse_version() >= 317) { + connInfo.setFeatureFlag(FuseConnInfo.FUSE_CAP_READDIRPLUS); + } else { + connInfo.setWant(connInfo.want() | FuseConnInfo.FUSE_CAP_READDIRPLUS); + } } var config = new FuseConfigImpl(cfg); fuseOperations.init(connInfo, config); diff --git a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseMountImpl.java b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseMountImpl.java index 89f6d322..7bcd54ab 100644 --- a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseMountImpl.java +++ b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseMountImpl.java @@ -1,6 +1,7 @@ package org.cryptomator.jfuse.mac; import org.cryptomator.jfuse.api.FuseMount; +import org.cryptomator.jfuse.api.FuseMountFailedException; import org.cryptomator.jfuse.mac.extr.fuse3.fuse_h; import org.cryptomator.jfuse.mac.extr.fuse3.fuse_loop_config_v1; @@ -14,12 +15,13 @@ record FuseMountImpl(MemorySegment fuse, FuseArgs fuseArgs) implements FuseMount @Override public int loop() { - // depends on fuse version: https://github.com/libfuse/libfuse/blob/fuse-3.12.0/include/fuse.h#L1011-L1050 + // macFUSE FSKit backend requires fuse_mount and fuse_loop on the same thread + if (fuse_h.fuse_mount(fuse, fuseArgs.mountPoint()) != 0) { + throw new RuntimeException(new FuseMountFailedException("fuse_mount failed")); + } if (!fuseArgs.multithreaded() || fuse_h.fuse_version() < FUSE_3_2) { - // FUSE 3.1: to keep things simple, we just don't support fuse_loop_mt return fuse_h.fuse_loop(fuse); } else if (fuse_h.fuse_version() < FUSE_3_12) { - // FUSE 3.2 try (var arena = Arena.ofConfined()) { var loopCfg = fuse_loop_config_v1.allocate(arena); fuse_loop_config_v1.clone_fd(loopCfg, fuseArgs.cloneFd()); @@ -27,7 +29,6 @@ public int loop() { return fuse_h.fuse_loop_mt(fuse, loopCfg); } } else { - // FUSE 3.12 var loopCfg = fuse_h.fuse_loop_cfg_create(); try { fuse_h.fuse_loop_cfg_set_clone_fd(loopCfg, fuseArgs.cloneFd()); diff --git a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseNewHelper.java b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseNewHelper.java index 261d95de..5abd785f 100644 --- a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseNewHelper.java +++ b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseNewHelper.java @@ -2,8 +2,10 @@ import org.cryptomator.jfuse.mac.extr.fuse3.fuse_h; +import java.lang.foreign.Arena; import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.Linker; +import java.lang.foreign.MemoryLayout; import java.lang.foreign.MemorySegment; import java.lang.foreign.SymbolLookup; import java.lang.foreign.ValueLayout; @@ -16,33 +18,68 @@ * This class is necessary due to changes in libfuse 3.17.1 and onwards: The function {@code fuse_new} is _not_ available anymore as unversioned symbol. * One can call the function either by also specifying the version or call the unversioned symbol {@code fuse_new_31}. *

- * Versioned symbols require a custom SymbolLookup (see PR #117), but libraries loaded via {@link System#loadLibrary(String)} cannot be accessed. - * Hence, this would require to _always_ set the libPath, loosing compatiblity to more exotic linux OSs with custom lib locations. - * To circumvent this issue, we call {@code fuse_version} to decide which (unversioned) symbol name we have to use. + * On macFUSE, we call {@code _fuse_new_31} (the 5-arg internal variant) directly, passing a properly + * initialized {@code libfuse_version} struct with {@code darwin_extensions_enabled=0}. + * This is required for FSKit backend support — the 4-arg wrapper zeros the version struct, + * which has the same effect, but calling the internal symbol directly makes the intent explicit + * and matches what fuse_main_real_versioned does. */ public class FuseNewHelper { private static final AtomicReference INSTANCE = new AtomicReference<>(null); private static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup().or(Linker.nativeLinker().defaultLookup()); - private static final FunctionDescriptor DESC = FunctionDescriptor.of( + + // 5-arg _fuse_new_31: (fuse_args*, fuse_operations*, size_t, libfuse_version*, void*) -> fuse* + private static final FunctionDescriptor DESC_5ARG = FunctionDescriptor.of( fuse_h.C_POINTER, fuse_h.C_POINTER, fuse_h.C_POINTER, fuse_h.C_LONG, + fuse_h.C_POINTER, fuse_h.C_POINTER ); + // 4-arg fallback: (fuse_args*, fuse_operations*, size_t, void*) -> fuse* + private static final FunctionDescriptor DESC_4ARG = FunctionDescriptor.of( + fuse_h.C_POINTER, + fuse_h.C_POINTER, + fuse_h.C_POINTER, + fuse_h.C_LONG, + fuse_h.C_POINTER + ); + + // libfuse_version struct: {uint32 major, uint32 minor, uint32 hotfix, uint32 flags} + private static final MemoryLayout LIBFUSE_VERSION_LAYOUT = MemoryLayout.structLayout( + ValueLayout.JAVA_INT.withName("major"), + ValueLayout.JAVA_INT.withName("minor"), + ValueLayout.JAVA_INT.withName("hotfix"), + ValueLayout.JAVA_INT.withName("flags") + ); + private final MethodHandle fuse_new; + private final boolean fiveArg; - private FuseNewHelper(String symbolName) { + private FuseNewHelper(String symbolName, boolean fiveArg) { + this.fiveArg = fiveArg; this.fuse_new = Linker.nativeLinker().downcallHandle( findOrThrow(symbolName), - DESC); + fiveArg ? DESC_5ARG : DESC_4ARG); } public MemorySegment fuse_new(MemorySegment args, MemorySegment op, long op_size, MemorySegment private_data) { try { - return (MemorySegment) fuse_new.invokeExact(args, op, op_size, private_data); + if (fiveArg) { + try (var arena = Arena.ofConfined()) { + var version = arena.allocate(LIBFUSE_VERSION_LAYOUT); + version.set(ValueLayout.JAVA_INT, 0, 3); // major + version.set(ValueLayout.JAVA_INT, 4, 18); // minor + version.set(ValueLayout.JAVA_INT, 8, 2); // hotfix + version.set(ValueLayout.JAVA_INT, 12, 1); // darwin_extensions_enabled=1 + return (MemorySegment) fuse_new.invokeExact(args, op, op_size, version, private_data); + } + } else { + return (MemorySegment) fuse_new.invokeExact(args, op, op_size, private_data); + } } catch (Throwable ex) { throw new AssertionError("should not reach here", ex); } @@ -57,9 +94,14 @@ public synchronized static FuseNewHelper getInstance() { private static FuseNewHelper createInstance() throws IllegalStateException { if (getLibVersion() < 317) { - return new FuseNewHelper("fuse_new"); + return new FuseNewHelper("fuse_new", false); } else { - return new FuseNewHelper("fuse_new_31"); + // Prefer _fuse_new_31 (5-arg internal) if available, fall back to fuse_new_31 (4-arg wrapper) + if (SYMBOL_LOOKUP.find("_fuse_new_31").isPresent()) { + return new FuseNewHelper("_fuse_new_31", true); + } else { + return new FuseNewHelper("fuse_new_31", false); + } } } diff --git a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/extr/fuse3/fuse_operations.java b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/extr/fuse3/fuse_operations.java index 0de384bc..2a4eea76 100644 --- a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/extr/fuse3/fuse_operations.java +++ b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/extr/fuse3/fuse_operations.java @@ -116,7 +116,8 @@ public class fuse_operations { fuse_h.C_POINTER.withName("lseek"), fuse_h.C_POINTER.withName("chflags"), fuse_h.C_POINTER.withName("setvolname"), - fuse_h.C_POINTER.withName("monitor") + fuse_h.C_POINTER.withName("monitor"), + fuse_h.C_POINTER.withName("statx") ).withName("fuse_operations"); /** diff --git a/jfuse-mac/src/test/java/org/cryptomator/jfuse/mac/FuseImplTest.java b/jfuse-mac/src/test/java/org/cryptomator/jfuse/mac/FuseImplTest.java index 3f2d7370..df8b9355 100644 --- a/jfuse-mac/src/test/java/org/cryptomator/jfuse/mac/FuseImplTest.java +++ b/jfuse-mac/src/test/java/org/cryptomator/jfuse/mac/FuseImplTest.java @@ -58,17 +58,16 @@ public void testFuseNewFails() { } @Test - @DisplayName("MountFailedException when fuse_mount fails") - public void testFuseMountFails() throws FuseMountFailedException { + @DisplayName("RuntimeException when fuse_mount fails in loop()") + public void testFuseMountFails() { try (var fuseH = Mockito.mockStatic(fuse_h.class)) { var fuseArgs = Mockito.mock(FuseArgs.class); - Mockito.doReturn(fuseArgs).when(fuseImplSpy).parseArgs(args); Mockito.when(fuseArgs.mountPoint()).thenReturn(MemorySegment.NULL); - Mockito.when(fuseArgs.args()).thenReturn(MemorySegment.NULL); fuseH.when(() -> fuse_h.fuse_mount(Mockito.any(), Mockito.any())).thenReturn(1); - Mockito.doReturn(MemorySegment.NULL).when(fuseImplSpy).createFuseFS(Mockito.any()); - var thrown = Assertions.assertThrows(FuseMountFailedException.class, () -> fuseImplSpy.mount(args)); - Assertions.assertEquals("fuse_mount failed", thrown.getMessage()); + var mount = new FuseMountImpl(MemorySegment.NULL, fuseArgs); + var thrown = Assertions.assertThrows(RuntimeException.class, mount::loop); + Assertions.assertInstanceOf(FuseMountFailedException.class, thrown.getCause()); + Assertions.assertEquals("fuse_mount failed", thrown.getCause().getMessage()); } } @@ -182,6 +181,7 @@ public void testInit316() { return null; }).when(fuseOps).init(Mockito.any(), Mockito.any()); var connInfo = fuse_conn_info.allocate(arena); + fuse_conn_info.capable(connInfo, (int) FuseConnInfo.FUSE_CAP_READDIRPLUS); var fuseConfig = fuse_config.allocate(arena); fuseH.when(fuse_h::fuse_version).thenReturn(316); @@ -207,12 +207,12 @@ public void testInit317() { return null; }).when(fuseOps).init(Mockito.any(), Mockito.any()); var connInfo = fuse_conn_info.allocate(arena); + fuse_conn_info.capable(connInfo, (int) FuseConnInfo.FUSE_CAP_READDIRPLUS); 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)); diff --git a/jfuse-mac/src/test/java/org/cryptomator/jfuse/mac/StatImplTest.java b/jfuse-mac/src/test/java/org/cryptomator/jfuse/mac/StatImplTest.java index 20ab93f1..0078d438 100644 --- a/jfuse-mac/src/test/java/org/cryptomator/jfuse/mac/StatImplTest.java +++ b/jfuse-mac/src/test/java/org/cryptomator/jfuse/mac/StatImplTest.java @@ -73,4 +73,4 @@ private interface SetInStat extends BiConsumer {} private interface GetInMemorySegment extends Function {} -} \ No newline at end of file +} diff --git a/jfuse-mac/src/test/java/org/cryptomator/jfuse/mac/StatfsImplTest.java b/jfuse-mac/src/test/java/org/cryptomator/jfuse/mac/StatfsImplTest.java index 3129804c..a3f4124b 100644 --- a/jfuse-mac/src/test/java/org/cryptomator/jfuse/mac/StatfsImplTest.java +++ b/jfuse-mac/src/test/java/org/cryptomator/jfuse/mac/StatfsImplTest.java @@ -2,7 +2,6 @@ import org.cryptomator.jfuse.api.Statvfs; import org.cryptomator.jfuse.mac.extr.fuse3.statfs; -import org.cryptomator.jfuse.mac.extr.fuse3.statvfs; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Named; @@ -57,10 +56,10 @@ private interface GetInStatvfs extends Function { @MethodSource public void testSetters(SetInStatvfs setter, GetInMemorySegment getter, Number value, long expected) { try (var arena = Arena.ofConfined()) { - var segment = statvfs.allocate(arena); - var statvfs = new StatfsImpl(segment); + var segment = statfs.allocate(arena); + var statfsImpl = new StatfsImpl(segment); - setter.accept(statvfs, value.longValue()); + setter.accept(statfsImpl, value.longValue()); Assertions.assertEquals(expected, getter.apply(segment).longValue()); } From 1f689771d2443b9743fd17b79bca9ca3d2fd06ca Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven Date: Sun, 26 Apr 2026 17:23:32 -0500 Subject: [PATCH 2/3] fix darwin_extensions_enabled=0 for vanilla struct stat bindings With darwin_extensions_enabled=1, macFUSE expects callbacks to use fuse_darwin_attr* instead of struct stat* and fuse_darwin_fill_dir_t instead of fuse_fill_dir_t. Our jextract bindings use the vanilla types, so extensions must be disabled for callbacks to work correctly. Co-Authored-By: Claude Opus 4.6 --- .../src/main/java/org/cryptomator/jfuse/mac/FuseNewHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseNewHelper.java b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseNewHelper.java index 5abd785f..fc0be0d2 100644 --- a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseNewHelper.java +++ b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseNewHelper.java @@ -74,7 +74,7 @@ public MemorySegment fuse_new(MemorySegment args, MemorySegment op, long op_size version.set(ValueLayout.JAVA_INT, 0, 3); // major version.set(ValueLayout.JAVA_INT, 4, 18); // minor version.set(ValueLayout.JAVA_INT, 8, 2); // hotfix - version.set(ValueLayout.JAVA_INT, 12, 1); // darwin_extensions_enabled=1 + version.set(ValueLayout.JAVA_INT, 12, 0); // darwin_extensions_enabled=0 return (MemorySegment) fuse_new.invokeExact(args, op, op_size, version, private_data); } } else { From bb6d9ca41e8c23b1d988149d507323176bfe604e Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven Date: Thu, 30 Apr 2026 19:13:16 -0500 Subject: [PATCH 3/3] enable macFUSE darwin extensions (fuse_darwin_attr) Use darwin_extensions_enabled=1 when calling _fuse_new_31, which gives us fuse_darwin_attr (with birth time and backup time) instead of struct stat in getattr/readdir callbacks. - Add fuse_darwin_attr.java struct binding (192 bytes) - StatImpl wraps fuse_darwin_attr for darwin extensions mode - StatCompatImpl wraps struct stat for fallback (older macFUSE) - DirFillerImpl allocates the right struct based on mode - FuseImpl queries FuseNewHelper.darwinExtensions() at init - Add Stat.backupTime() default method (null on non-darwin) Co-Authored-By: Claude Opus 4.6 --- .../java/org/cryptomator/jfuse/api/Stat.java | 10 + .../cryptomator/jfuse/mac/DirFillerImpl.java | 19 +- .../org/cryptomator/jfuse/mac/FuseImpl.java | 18 +- .../cryptomator/jfuse/mac/FuseNewHelper.java | 8 +- .../cryptomator/jfuse/mac/StatCompatImpl.java | 81 +++++++ .../org/cryptomator/jfuse/mac/StatImpl.java | 35 +-- .../mac/extr/fuse3/fuse_darwin_attr.java | 205 ++++++++++++++++++ .../cryptomator/jfuse/mac/StatImplTest.java | 26 +-- 8 files changed, 363 insertions(+), 39 deletions(-) create mode 100644 jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/StatCompatImpl.java create mode 100644 jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/extr/fuse3/fuse_darwin_attr.java diff --git a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/Stat.java b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/Stat.java index 9ddf212a..5d39f2d6 100644 --- a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/Stat.java +++ b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/Stat.java @@ -123,6 +123,16 @@ public interface Stat { */ TimeSpec birthTime(); + /** + * Backup time (macOS-specific, via macFUSE darwin extensions). + * Returns {@code null} on platforms that don't support it. + * + * @return backup time value, or {@code null} if unsupported + */ + default TimeSpec backupTime() { + return null; + } + /** * Set {@link #getMode() mode}. * diff --git a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/DirFillerImpl.java b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/DirFillerImpl.java index 1599fe34..eed50459 100644 --- a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/DirFillerImpl.java +++ b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/DirFillerImpl.java @@ -2,6 +2,7 @@ import org.cryptomator.jfuse.api.DirFiller; import org.cryptomator.jfuse.api.Stat; +import org.cryptomator.jfuse.mac.extr.fuse3.fuse_darwin_attr; import org.cryptomator.jfuse.mac.extr.fuse3.fuse_fill_dir_t; import org.cryptomator.jfuse.mac.extr.fuse3.stat; @@ -9,13 +10,21 @@ import java.lang.foreign.MemorySegment; import java.util.function.Consumer; -record DirFillerImpl(MemorySegment buf, MemorySegment callback, Arena arena) implements DirFiller { +record DirFillerImpl(MemorySegment buf, MemorySegment callback, Arena arena, boolean darwinExtensions) implements DirFiller { @Override public int fill(String name, Consumer statFiller, long offset, int flags) { - var statSegment = stat.allocate(arena); - statFiller.accept(new StatImpl(statSegment)); - return fuse_fill_dir_t.invoke(callback, buf, arena.allocateFrom(name), statSegment, offset, flags); + MemorySegment segment; + Stat statWrapper; + if (darwinExtensions) { + segment = fuse_darwin_attr.allocate(arena); + statWrapper = new StatImpl(segment); + } else { + segment = stat.allocate(arena); + statWrapper = new StatCompatImpl(segment); + } + statFiller.accept(statWrapper); + return fuse_fill_dir_t.invoke(callback, buf, arena.allocateFrom(name), segment, offset, flags); } -} \ No newline at end of file +} diff --git a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseImpl.java b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseImpl.java index 368b179b..15a2c8cb 100644 --- a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseImpl.java +++ b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseImpl.java @@ -2,6 +2,7 @@ import org.cryptomator.jfuse.api.Fuse; import org.cryptomator.jfuse.api.FuseConnInfo; +import org.cryptomator.jfuse.api.Stat; import org.cryptomator.jfuse.api.FuseMount; import org.cryptomator.jfuse.api.FuseMountFailedException; import org.cryptomator.jfuse.api.FuseOperations; @@ -22,6 +23,8 @@ final class FuseImpl extends Fuse { + private volatile boolean darwinExtensions; + public FuseImpl(FuseOperations fuseOperations) { super(fuseOperations, fuse_operations::allocate); } @@ -50,7 +53,9 @@ private static List ensureVolname(List args) { @VisibleForTesting MemorySegment createFuseFS(FuseArgs fuseArgs) throws FuseMountFailedException { - var fuse = FuseNewHelper.getInstance().fuse_new(fuseArgs.args(), fuseOperationsStruct, fuseOperationsStruct.byteSize(), MemorySegment.NULL); + var helper = FuseNewHelper.getInstance(); + this.darwinExtensions = helper.darwinExtensions(); + var fuse = helper.fuse_new(fuseArgs.args(), fuseOperationsStruct, fuseOperationsStruct.byteSize(), MemorySegment.NULL); if (MemorySegment.NULL.equals(fuse)) { throw new FuseMountFailedException("fuse_new failed"); } @@ -174,14 +179,18 @@ int fsyncdir(MemorySegment path, int datasync, MemorySegment fi) { return fuseOperations.fsyncdir(MemoryUtils.toUtf8StringOrNull(path), datasync, new FileInfoImpl(fi)); } + private Stat wrapStat(MemorySegment segment) { + return darwinExtensions ? new StatImpl(segment) : new StatCompatImpl(segment); + } + @VisibleForTesting int getattr(MemorySegment path, MemorySegment stat, MemorySegment fi) { - return fuseOperations.getattr(path.getString(0), new StatImpl(stat), FileInfoImpl.ofNullable(fi)); + return fuseOperations.getattr(path.getString(0), wrapStat(stat), FileInfoImpl.ofNullable(fi)); } @VisibleForTesting int fgetattr(MemorySegment path, MemorySegment stat, MemorySegment fi) { - return fuseOperations.getattr(path.getString(0), new StatImpl(stat), new FileInfoImpl(fi)); + return fuseOperations.getattr(path.getString(0), wrapStat(stat), new FileInfoImpl(fi)); } @VisibleForTesting @@ -226,7 +235,7 @@ private int read(MemorySegment path, MemorySegment buf, long size, long offset, private int readdir(MemorySegment path, MemorySegment buf, MemorySegment filler, long offset, MemorySegment fi, int flags) { try (var arena = Arena.ofConfined()) { - return fuseOperations.readdir(path.getString(0), new DirFillerImpl(buf, filler, arena), offset, new FileInfoImpl(fi), flags); + return fuseOperations.readdir(path.getString(0), new DirFillerImpl(buf, filler, arena, darwinExtensions), offset, new FileInfoImpl(fi), flags); } } @@ -277,7 +286,6 @@ private int unlink(MemorySegment path) { int utimens(MemorySegment path, MemorySegment times, MemorySegment fi) { try (var arena = Arena.ofConfined()) { if (MemorySegment.NULL.equals(times)) { - // set both times to current time var segment = timespec.allocate(arena); timespec.tv_sec(segment, 0); timespec.tv_nsec(segment, stat_h.UTIME_NOW()); diff --git a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseNewHelper.java b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseNewHelper.java index fc0be0d2..048fcdd9 100644 --- a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseNewHelper.java +++ b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/FuseNewHelper.java @@ -58,9 +58,11 @@ public class FuseNewHelper { private final MethodHandle fuse_new; private final boolean fiveArg; + private final boolean darwinExtensions; private FuseNewHelper(String symbolName, boolean fiveArg) { this.fiveArg = fiveArg; + this.darwinExtensions = fiveArg; this.fuse_new = Linker.nativeLinker().downcallHandle( findOrThrow(symbolName), fiveArg ? DESC_5ARG : DESC_4ARG); @@ -74,7 +76,7 @@ public MemorySegment fuse_new(MemorySegment args, MemorySegment op, long op_size version.set(ValueLayout.JAVA_INT, 0, 3); // major version.set(ValueLayout.JAVA_INT, 4, 18); // minor version.set(ValueLayout.JAVA_INT, 8, 2); // hotfix - version.set(ValueLayout.JAVA_INT, 12, 0); // darwin_extensions_enabled=0 + version.set(ValueLayout.JAVA_INT, 12, 1); // darwin_extensions_enabled=1 return (MemorySegment) fuse_new.invokeExact(args, op, op_size, version, private_data); } } else { @@ -85,6 +87,10 @@ public MemorySegment fuse_new(MemorySegment args, MemorySegment op, long op_size } } + public boolean darwinExtensions() { + return darwinExtensions; + } + public synchronized static FuseNewHelper getInstance() { if (INSTANCE.get() == null) { INSTANCE.set(createInstance()); diff --git a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/StatCompatImpl.java b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/StatCompatImpl.java new file mode 100644 index 00000000..4fa085ce --- /dev/null +++ b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/StatCompatImpl.java @@ -0,0 +1,81 @@ +package org.cryptomator.jfuse.mac; + +import org.cryptomator.jfuse.api.Stat; +import org.cryptomator.jfuse.api.TimeSpec; +import org.cryptomator.jfuse.mac.extr.fuse3.stat; + +import java.lang.foreign.MemorySegment; + +record StatCompatImpl(MemorySegment segment) implements Stat { + + @Override + public TimeSpec aTime() { + return new TimeSpecImpl(stat.st_atimespec(segment)); + } + + @Override + public TimeSpec cTime() { + return new TimeSpecImpl(stat.st_ctimespec(segment)); + } + + @Override + public TimeSpec mTime() { + return new TimeSpecImpl(stat.st_mtimespec(segment)); + } + + @Override + public TimeSpec birthTime() { + return new TimeSpecImpl(stat.st_birthtimespec(segment)); + } + + @Override + public void setMode(int mode) { + stat.st_mode(segment, (short) mode); + } + + @Override + public int getMode() { + return stat.st_mode(segment); + } + + @Override + public void setUid(int uid) { + stat.st_uid(segment, uid); + } + + @Override + public int getUid() { + return stat.st_uid(segment); + } + + @Override + public void setGid(int gid) { + stat.st_gid(segment, gid); + } + + @Override + public int getGid() { + return stat.st_gid(segment); + } + + @Override + public void setNLink(short count) { + stat.st_nlink(segment, count); + } + + @Override + public long getNLink() { + return stat.st_nlink(segment); + } + + @Override + public void setSize(long size) { + stat.st_size(segment, size); + } + + @Override + public long getSize() { + return stat.st_size(segment); + } + +} diff --git a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/StatImpl.java b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/StatImpl.java index a560c4c3..b8981a2b 100644 --- a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/StatImpl.java +++ b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/StatImpl.java @@ -2,7 +2,7 @@ import org.cryptomator.jfuse.api.Stat; import org.cryptomator.jfuse.api.TimeSpec; -import org.cryptomator.jfuse.mac.extr.fuse3.stat; +import org.cryptomator.jfuse.mac.extr.fuse3.fuse_darwin_attr; import java.lang.foreign.MemorySegment; @@ -10,72 +10,77 @@ record StatImpl(MemorySegment segment) implements Stat { @Override public TimeSpec aTime() { - return new TimeSpecImpl(stat.st_atimespec(segment)); + return new TimeSpecImpl(fuse_darwin_attr.atimespec(segment)); } @Override public TimeSpec cTime() { - return new TimeSpecImpl(stat.st_ctimespec(segment)); + return new TimeSpecImpl(fuse_darwin_attr.ctimespec(segment)); } @Override public TimeSpec mTime() { - return new TimeSpecImpl(stat.st_mtimespec(segment)); + return new TimeSpecImpl(fuse_darwin_attr.mtimespec(segment)); } @Override public TimeSpec birthTime() { - return new TimeSpecImpl(stat.st_birthtimespec(segment)); + return new TimeSpecImpl(fuse_darwin_attr.btimespec(segment)); + } + + @Override + public TimeSpec backupTime() { + return new TimeSpecImpl(fuse_darwin_attr.bkuptimespec(segment)); } @Override public void setMode(int mode) { - stat.st_mode(segment, (short) mode); + fuse_darwin_attr.mode(segment, (short) mode); } @Override public int getMode() { - return stat.st_mode(segment); + return fuse_darwin_attr.mode(segment); } @Override public void setUid(int uid) { - stat.st_uid(segment, uid); + fuse_darwin_attr.uid(segment, uid); } @Override public int getUid() { - return stat.st_uid(segment); + return fuse_darwin_attr.uid(segment); } @Override public void setGid(int gid) { - stat.st_gid(segment, gid); + fuse_darwin_attr.gid(segment, gid); } @Override public int getGid() { - return stat.st_gid(segment); + return fuse_darwin_attr.gid(segment); } @Override public void setNLink(short count) { - stat.st_nlink(segment, count); + fuse_darwin_attr.nlink(segment, count); } @Override public long getNLink() { - return stat.st_nlink(segment); + return fuse_darwin_attr.nlink(segment); } @Override public void setSize(long size) { - stat.st_size(segment, size); + fuse_darwin_attr.size(segment, size); } @Override public long getSize() { - return stat.st_size(segment); + return fuse_darwin_attr.size(segment); } } diff --git a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/extr/fuse3/fuse_darwin_attr.java b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/extr/fuse3/fuse_darwin_attr.java new file mode 100644 index 00000000..3e18d3ee --- /dev/null +++ b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/extr/fuse3/fuse_darwin_attr.java @@ -0,0 +1,205 @@ +package org.cryptomator.jfuse.mac.extr.fuse3; + +import java.lang.foreign.*; +import java.util.function.Consumer; + +import static java.lang.foreign.MemoryLayout.PathElement.*; + +/** + * {@snippet lang=c : + * struct fuse_darwin_attr { + * ino_t ino; // offset 0, 8 bytes + * mode_t mode; // offset 8, 2 bytes + * nlink_t nlink; // offset 10, 2 bytes + * uid_t uid; // offset 12, 4 bytes + * gid_t gid; // offset 16, 4 bytes + * dev_t rdev; // offset 20, 4 bytes + * struct timespec atimespec; // offset 24, 16 bytes + * struct timespec mtimespec; // offset 40, 16 bytes + * struct timespec ctimespec; // offset 56, 16 bytes + * struct timespec btimespec; // offset 72, 16 bytes + * struct timespec bkuptimespec; // offset 88, 16 bytes + * off_t size; // offset 104, 8 bytes + * blkcnt_t blocks; // offset 112, 8 bytes + * blksize_t blksize; // offset 120, 4 bytes + * unsigned int flags; // offset 124, 4 bytes + * uint64_t reserved[8]; // offset 128, 64 bytes + * } + * } + */ +public class fuse_darwin_attr { + + fuse_darwin_attr() {} + + private static final GroupLayout $LAYOUT = MemoryLayout.structLayout( + fuse_h.C_LONG_LONG.withName("ino"), + fuse_h.C_SHORT.withName("mode"), + fuse_h.C_SHORT.withName("nlink"), + fuse_h.C_INT.withName("uid"), + fuse_h.C_INT.withName("gid"), + fuse_h.C_INT.withName("rdev"), + timespec.layout().withName("atimespec"), + timespec.layout().withName("mtimespec"), + timespec.layout().withName("ctimespec"), + timespec.layout().withName("btimespec"), + timespec.layout().withName("bkuptimespec"), + fuse_h.C_LONG_LONG.withName("size"), + fuse_h.C_LONG_LONG.withName("blocks"), + fuse_h.C_INT.withName("blksize"), + fuse_h.C_INT.withName("flags"), + MemoryLayout.sequenceLayout(8, fuse_h.C_LONG_LONG).withName("reserved") + ).withName("fuse_darwin_attr"); + + public static GroupLayout layout() { + return $LAYOUT; + } + + // ino: offset 0 + private static final long ino$OFFSET = 0; + public static long ino(MemorySegment struct) { + return struct.get(fuse_h.C_LONG_LONG, ino$OFFSET); + } + public static void ino(MemorySegment struct, long fieldValue) { + struct.set(fuse_h.C_LONG_LONG, ino$OFFSET, fieldValue); + } + + // mode: offset 8 + private static final long mode$OFFSET = 8; + public static short mode(MemorySegment struct) { + return struct.get(fuse_h.C_SHORT, mode$OFFSET); + } + public static void mode(MemorySegment struct, short fieldValue) { + struct.set(fuse_h.C_SHORT, mode$OFFSET, fieldValue); + } + + // nlink: offset 10 + private static final long nlink$OFFSET = 10; + public static short nlink(MemorySegment struct) { + return struct.get(fuse_h.C_SHORT, nlink$OFFSET); + } + public static void nlink(MemorySegment struct, short fieldValue) { + struct.set(fuse_h.C_SHORT, nlink$OFFSET, fieldValue); + } + + // uid: offset 12 + private static final long uid$OFFSET = 12; + public static int uid(MemorySegment struct) { + return struct.get(fuse_h.C_INT, uid$OFFSET); + } + public static void uid(MemorySegment struct, int fieldValue) { + struct.set(fuse_h.C_INT, uid$OFFSET, fieldValue); + } + + // gid: offset 16 + private static final long gid$OFFSET = 16; + public static int gid(MemorySegment struct) { + return struct.get(fuse_h.C_INT, gid$OFFSET); + } + public static void gid(MemorySegment struct, int fieldValue) { + struct.set(fuse_h.C_INT, gid$OFFSET, fieldValue); + } + + // rdev: offset 20 + private static final long rdev$OFFSET = 20; + public static int rdev(MemorySegment struct) { + return struct.get(fuse_h.C_INT, rdev$OFFSET); + } + public static void rdev(MemorySegment struct, int fieldValue) { + struct.set(fuse_h.C_INT, rdev$OFFSET, fieldValue); + } + + // atimespec: offset 24 + private static final long atimespec$OFFSET = 24; + public static MemorySegment atimespec(MemorySegment struct) { + return struct.asSlice(atimespec$OFFSET, timespec.layout().byteSize()); + } + public static void atimespec(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, atimespec$OFFSET, timespec.layout().byteSize()); + } + + // mtimespec: offset 40 + private static final long mtimespec$OFFSET = 40; + public static MemorySegment mtimespec(MemorySegment struct) { + return struct.asSlice(mtimespec$OFFSET, timespec.layout().byteSize()); + } + public static void mtimespec(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, mtimespec$OFFSET, timespec.layout().byteSize()); + } + + // ctimespec: offset 56 + private static final long ctimespec$OFFSET = 56; + public static MemorySegment ctimespec(MemorySegment struct) { + return struct.asSlice(ctimespec$OFFSET, timespec.layout().byteSize()); + } + public static void ctimespec(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, ctimespec$OFFSET, timespec.layout().byteSize()); + } + + // btimespec (birth time): offset 72 + private static final long btimespec$OFFSET = 72; + public static MemorySegment btimespec(MemorySegment struct) { + return struct.asSlice(btimespec$OFFSET, timespec.layout().byteSize()); + } + public static void btimespec(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, btimespec$OFFSET, timespec.layout().byteSize()); + } + + // bkuptimespec (backup time): offset 88 + private static final long bkuptimespec$OFFSET = 88; + public static MemorySegment bkuptimespec(MemorySegment struct) { + return struct.asSlice(bkuptimespec$OFFSET, timespec.layout().byteSize()); + } + public static void bkuptimespec(MemorySegment struct, MemorySegment fieldValue) { + MemorySegment.copy(fieldValue, 0L, struct, bkuptimespec$OFFSET, timespec.layout().byteSize()); + } + + // size: offset 104 + private static final long size$OFFSET = 104; + public static long size(MemorySegment struct) { + return struct.get(fuse_h.C_LONG_LONG, size$OFFSET); + } + public static void size(MemorySegment struct, long fieldValue) { + struct.set(fuse_h.C_LONG_LONG, size$OFFSET, fieldValue); + } + + // blocks: offset 112 + private static final long blocks$OFFSET = 112; + public static long blocks(MemorySegment struct) { + return struct.get(fuse_h.C_LONG_LONG, blocks$OFFSET); + } + public static void blocks(MemorySegment struct, long fieldValue) { + struct.set(fuse_h.C_LONG_LONG, blocks$OFFSET, fieldValue); + } + + // blksize: offset 120 + private static final long blksize$OFFSET = 120; + public static int blksize(MemorySegment struct) { + return struct.get(fuse_h.C_INT, blksize$OFFSET); + } + public static void blksize(MemorySegment struct, int fieldValue) { + struct.set(fuse_h.C_INT, blksize$OFFSET, fieldValue); + } + + // flags: offset 124 + private static final long flags$OFFSET = 124; + public static int flags(MemorySegment struct) { + return struct.get(fuse_h.C_INT, flags$OFFSET); + } + public static void flags(MemorySegment struct, int fieldValue) { + struct.set(fuse_h.C_INT, flags$OFFSET, fieldValue); + } + + public static long sizeof() { return layout().byteSize(); } + + public static MemorySegment allocate(SegmentAllocator allocator) { + return allocator.allocate(layout()); + } + + public static MemorySegment allocateArray(long elementCount, SegmentAllocator allocator) { + return allocator.allocate(MemoryLayout.sequenceLayout(elementCount, layout())); + } + + public static MemorySegment asSlice(MemorySegment array, long index) { + return array.asSlice(layout().byteSize() * index); + } +} diff --git a/jfuse-mac/src/test/java/org/cryptomator/jfuse/mac/StatImplTest.java b/jfuse-mac/src/test/java/org/cryptomator/jfuse/mac/StatImplTest.java index 0078d438..599c3774 100644 --- a/jfuse-mac/src/test/java/org/cryptomator/jfuse/mac/StatImplTest.java +++ b/jfuse-mac/src/test/java/org/cryptomator/jfuse/mac/StatImplTest.java @@ -1,7 +1,7 @@ package org.cryptomator.jfuse.mac; import org.cryptomator.jfuse.api.Stat; -import org.cryptomator.jfuse.mac.extr.fuse3.stat; +import org.cryptomator.jfuse.mac.extr.fuse3.fuse_darwin_attr; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Named; @@ -22,7 +22,7 @@ public class StatImplTest { @MethodSource public void testGetters(SetInMemorySegment setter, GetInStat getter, Number value) { try (var arena = Arena.ofConfined()) { - var segment = stat.allocate(arena); + var segment = fuse_darwin_attr.allocate(arena); var stat = new StatImpl(segment); setter.accept(segment, value); @@ -33,11 +33,11 @@ public void testGetters(SetInMemorySegment setter, GetInStat get public static Stream testGetters() { return Stream.of( - Arguments.arguments((SetInMemorySegment) stat::st_mode, Named.of("getMode()", (GetInStat) Stat::getMode), (short) 42), - Arguments.arguments((SetInMemorySegment) stat::st_uid, Named.of("getUid()", (GetInStat) Stat::getUid), 42), - Arguments.arguments((SetInMemorySegment) stat::st_gid, Named.of("getGid()", (GetInStat) Stat::getGid), 42), - Arguments.arguments((SetInMemorySegment) stat::st_nlink, Named.of("getNLink()", (GetInStat) Stat::getNLink), (short) 42), - Arguments.arguments((SetInMemorySegment) stat::st_size, Named.of("getSize()", (GetInStat) Stat::getSize), 42L) + Arguments.arguments((SetInMemorySegment) fuse_darwin_attr::mode, Named.of("getMode()", (GetInStat) Stat::getMode), (short) 42), + Arguments.arguments((SetInMemorySegment) fuse_darwin_attr::uid, Named.of("getUid()", (GetInStat) Stat::getUid), 42), + Arguments.arguments((SetInMemorySegment) fuse_darwin_attr::gid, Named.of("getGid()", (GetInStat) Stat::getGid), 42), + Arguments.arguments((SetInMemorySegment) fuse_darwin_attr::nlink, Named.of("getNLink()", (GetInStat) Stat::getNLink), (short) 42), + Arguments.arguments((SetInMemorySegment) fuse_darwin_attr::size, Named.of("getSize()", (GetInStat) Stat::getSize), 42L) ); } @@ -50,7 +50,7 @@ private interface GetInStat extends Function {} @MethodSource public void testSetters(SetInStat setter, GetInMemorySegment getter, Number value) { try (var arena = Arena.ofConfined()) { - var segment = stat.allocate(arena); + var segment = fuse_darwin_attr.allocate(arena); var stat = new StatImpl(segment); setter.accept(stat, value); @@ -61,11 +61,11 @@ public void testSetters(SetInStat setter, GetInMemorySegment get public static Stream testSetters() { return Stream.of( - Arguments.arguments(Named.of("setMode()", (SetInStat) Stat::setMode), (GetInMemorySegment) stat::st_mode, 42), - Arguments.arguments(Named.of("setUid()", (SetInStat) Stat::setUid), (GetInMemorySegment) stat::st_uid, 42), - Arguments.arguments(Named.of("setGid()", (SetInStat) Stat::setGid), (GetInMemorySegment) stat::st_gid, 42), - Arguments.arguments(Named.of("setNLink()", (SetInStat) Stat::setNLink), (GetInMemorySegment) stat::st_nlink, (short) 42), - Arguments.arguments(Named.of("setSize()", (SetInStat) Stat::setSize), (GetInMemorySegment) stat::st_size, 42L) + Arguments.arguments(Named.of("setMode()", (SetInStat) Stat::setMode), (GetInMemorySegment) fuse_darwin_attr::mode, 42), + Arguments.arguments(Named.of("setUid()", (SetInStat) Stat::setUid), (GetInMemorySegment) fuse_darwin_attr::uid, 42), + Arguments.arguments(Named.of("setGid()", (SetInStat) Stat::setGid), (GetInMemorySegment) fuse_darwin_attr::gid, 42), + Arguments.arguments(Named.of("setNLink()", (SetInStat) Stat::setNLink), (GetInMemorySegment) fuse_darwin_attr::nlink, (short) 42), + Arguments.arguments(Named.of("setSize()", (SetInStat) Stat::setSize), (GetInMemorySegment) fuse_darwin_attr::size, 42L) ); }