diff --git a/jfuse-api/src/main/java/module-info.java b/jfuse-api/src/main/java/module-info.java index 45a46b4f..16df27ab 100644 --- a/jfuse-api/src/main/java/module-info.java +++ b/jfuse-api/src/main/java/module-info.java @@ -10,6 +10,7 @@ exports org.cryptomator.jfuse.api; exports org.cryptomator.jfuse.api.platforms to org.cryptomator.jfuse.linux.aarch64, org.cryptomator.jfuse.linux.amd64, org.cryptomator.jfuse.mac, org.cryptomator.jfuse.win; + exports org.cryptomator.jfuse.api.util to org.cryptomator.jfuse.linux.aarch64, org.cryptomator.jfuse.linux.amd64, org.cryptomator.jfuse.mac, org.cryptomator.jfuse.win; uses FuseBuilder; } \ No newline at end of file diff --git a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/util/MemoryUtils.java b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/util/MemoryUtils.java new file mode 100644 index 00000000..100c3b73 --- /dev/null +++ b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/util/MemoryUtils.java @@ -0,0 +1,18 @@ +package org.cryptomator.jfuse.api.util; + +import org.jetbrains.annotations.Nullable; + +import java.lang.foreign.MemorySegment; + +public class MemoryUtils { + + @Nullable + public static String toUtf8StringOrNull(MemorySegment string, long offset) { + return MemorySegment.NULL.equals(string) ? null : string.getUtf8String(offset); + } + + @Nullable + public static String toUtf8StringOrNull(MemorySegment string) { + return toUtf8StringOrNull(string, 0); + } +} diff --git a/jfuse-api/src/test/java/org/cryptomator/jfuse/api/util/MemoryUtilsTest.java b/jfuse-api/src/test/java/org/cryptomator/jfuse/api/util/MemoryUtilsTest.java new file mode 100644 index 00000000..ad4c2ab8 --- /dev/null +++ b/jfuse-api/src/test/java/org/cryptomator/jfuse/api/util/MemoryUtilsTest.java @@ -0,0 +1,41 @@ +package org.cryptomator.jfuse.api.util; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; + +public class MemoryUtilsTest { + + + @Test + @DisplayName("On MemorySegment != NULL pointer, method returns utf8 string in the memory region") + void testValidSegmentReturnsString() { + try (var arena = Arena.ofConfined()) { + var address = arena.allocate(4); + address.setUtf8String(0, "abc"); + String result = MemoryUtils.toUtf8StringOrNull(address); + Assertions.assertEquals("abc", result); + } + } + + @Test + @DisplayName("With offset, on MemorySegment != NULL pointer, method returns utf8 string in the memory region") + void testValidSegmentReturnsStringAtOffset() { + try (var arena = Arena.ofConfined()) { + var address = arena.allocate(10); + address.setUtf8String(5, "abc"); + String result = MemoryUtils.toUtf8StringOrNull(address, 5); + Assertions.assertEquals("abc", result); + } + } + + @Test + @DisplayName("When MemorySegment == NULL pointer, method returns null") + void testNullPointerSegmentReturnsNull() { + String result = MemoryUtils.toUtf8StringOrNull(MemorySegment.NULL, 0); + Assertions.assertNull(result); + } +} diff --git a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FileInfoImpl.java b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FileInfoImpl.java index 9f83c786..e3017038 100644 --- a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FileInfoImpl.java +++ b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/FileInfoImpl.java @@ -3,6 +3,7 @@ import org.cryptomator.jfuse.api.FileInfo; import org.cryptomator.jfuse.linux.aarch64.extr.fcntl.fcntl_h; import org.cryptomator.jfuse.linux.aarch64.extr.fuse3.fuse_file_info; +import org.jetbrains.annotations.Nullable; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; @@ -22,6 +23,18 @@ record FileInfoImpl(MemorySegment segment) implements FileInfo { private static final int O_SYNC = fcntl_h.O_SYNC(); private static final int O_DSYNC = fcntl_h.O_DSYNC(); + /** + * Factory method to map native memory to an {@link FileInfo} object + * + * @param address the {@link MemorySegment} representing the starting address + * @param scope the {@link Arena} in which this object will be alive + * @return an {@link FileInfo} object or {@code null} if {@code address} is a NULL pointer + */ + @Nullable + public static FileInfoImpl of(MemorySegment address, Arena scope) { + return MemorySegment.NULL.equals(address) ? null : new FileInfoImpl(address, scope); + } + public FileInfoImpl(MemorySegment address, Arena scope) { this(fuse_file_info.ofAddress(address, scope)); } 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 5442c7f0..f2a91ace 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 @@ -5,6 +5,7 @@ import org.cryptomator.jfuse.api.FuseMount; import org.cryptomator.jfuse.api.FuseMountFailedException; import org.cryptomator.jfuse.api.FuseOperations; +import org.cryptomator.jfuse.api.util.MemoryUtils; import org.cryptomator.jfuse.linux.aarch64.extr.fuse3.fuse_args; import org.cryptomator.jfuse.linux.aarch64.extr.fuse3.fuse_h; import org.cryptomator.jfuse.linux.aarch64.extr.fuse3.fuse_operations; @@ -116,14 +117,14 @@ private int access(MemorySegment path, int mask) { private int chmod(MemorySegment path, int mode, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.chmod(path.getUtf8String(0), mode, new FileInfoImpl(fi, arena)); + return fuseOperations.chmod(path.getUtf8String(0), mode, FileInfoImpl.of(fi, arena)); } } @VisibleForTesting int chown(MemorySegment path, int uid, int gid, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.chown(path.getUtf8String(0), uid, gid, new FileInfoImpl(fi, arena)); + return fuseOperations.chown(path.getUtf8String(0), uid, gid, FileInfoImpl.of(fi, arena)); } } @@ -154,13 +155,13 @@ int fsync(MemorySegment path, int datasync, MemorySegment fi) { @VisibleForTesting int fsyncdir(MemorySegment path, int datasync, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.fsyncdir(path.getUtf8String(0), datasync, new FileInfoImpl(fi, arena)); + return fuseOperations.fsyncdir(MemoryUtils.toUtf8StringOrNull(path), datasync, new FileInfoImpl(fi, arena)); } } private int getattr(MemorySegment path, MemorySegment stat, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.getattr(path.getUtf8String(0), new StatImpl(stat, arena), new FileInfoImpl(fi, arena)); + return fuseOperations.getattr(path.getUtf8String(0), new StatImpl(stat, arena), FileInfoImpl.of(fi, arena)); } } @@ -229,7 +230,7 @@ private int release(MemorySegment path, MemorySegment fi) { private int releasedir(MemorySegment path, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.releasedir(path.getUtf8String(0), new FileInfoImpl(fi, arena)); + return fuseOperations.releasedir(MemoryUtils.toUtf8StringOrNull(path), new FileInfoImpl(fi, arena)); } } @@ -253,7 +254,7 @@ private int symlink(MemorySegment linkname, MemorySegment target) { private int truncate(MemorySegment path, long size, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.truncate(path.getUtf8String(0), size, new FileInfoImpl(fi, arena)); + return fuseOperations.truncate(path.getUtf8String(0), size, FileInfoImpl.of(fi, arena)); } } @@ -271,11 +272,11 @@ int utimens(MemorySegment path, MemorySegment times, MemorySegment fi) { timespec.tv_sec$set(segment, 0); timespec.tv_nsec$set(segment, stat_h.UTIME_NOW()); var time = new TimeSpecImpl(segment); - return fuseOperations.utimens(path.getUtf8String(0), time, time, new FileInfoImpl(fi, arena)); + return fuseOperations.utimens(path.getUtf8String(0), time, time, FileInfoImpl.of(fi, arena)); } else { var time0 = times.asSlice(0, timespec.$LAYOUT().byteSize()); var time1 = times.asSlice(timespec.$LAYOUT().byteSize(), timespec.$LAYOUT().byteSize()); - return fuseOperations.utimens(path.getUtf8String(0), new TimeSpecImpl(time0), new TimeSpecImpl(time1), new FileInfoImpl(fi, arena)); + return fuseOperations.utimens(path.getUtf8String(0), new TimeSpecImpl(time0), new TimeSpecImpl(time1), FileInfoImpl.of(fi, arena)); } } } diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FileInfoImpl.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FileInfoImpl.java index eda24c72..e687f6fa 100644 --- a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FileInfoImpl.java +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/FileInfoImpl.java @@ -3,6 +3,7 @@ import org.cryptomator.jfuse.api.FileInfo; import org.cryptomator.jfuse.linux.amd64.extr.fcntl.fcntl_h; import org.cryptomator.jfuse.linux.amd64.extr.fuse3.fuse_file_info; +import org.jetbrains.annotations.Nullable; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; @@ -22,6 +23,18 @@ record FileInfoImpl(MemorySegment segment) implements FileInfo { private static final int O_SYNC = fcntl_h.O_SYNC(); private static final int O_DSYNC = fcntl_h.O_DSYNC(); + /** + * Factory method to map native memory to an {@link FileInfo} object + * + * @param address the {@link MemorySegment} representing the starting address + * @param scope the {@link Arena} in which this object will be alive + * @return an {@link FileInfo} object or {@code null} if {@code address} is a NULL pointer + */ + @Nullable + public static FileInfoImpl of(MemorySegment address, Arena scope) { + return MemorySegment.NULL.equals(address) ? null : new FileInfoImpl(address, scope); + } + public FileInfoImpl(MemorySegment address, Arena scope) { this(fuse_file_info.ofAddress(address, scope)); } 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 e6dfe26c..dc4ef78c 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 @@ -5,6 +5,7 @@ import org.cryptomator.jfuse.api.FuseMount; import org.cryptomator.jfuse.api.FuseMountFailedException; import org.cryptomator.jfuse.api.FuseOperations; +import org.cryptomator.jfuse.api.util.MemoryUtils; import org.cryptomator.jfuse.linux.amd64.extr.fuse3.fuse_args; import org.cryptomator.jfuse.linux.amd64.extr.fuse3.fuse_h; import org.cryptomator.jfuse.linux.amd64.extr.fuse3.fuse_operations; @@ -116,14 +117,14 @@ private int access(MemorySegment path, int mask) { private int chmod(MemorySegment path, int mode, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.chmod(path.getUtf8String(0), mode, new FileInfoImpl(fi, arena)); + return fuseOperations.chmod(path.getUtf8String(0), mode, FileInfoImpl.of(fi, arena)); } } @VisibleForTesting int chown(MemorySegment path, int uid, int gid, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.chown(path.getUtf8String(0), uid, gid, new FileInfoImpl(fi, arena)); + return fuseOperations.chown(path.getUtf8String(0), uid, gid, FileInfoImpl.of(fi, arena)); } } @@ -154,13 +155,13 @@ int fsync(MemorySegment path, int datasync, MemorySegment fi) { @VisibleForTesting int fsyncdir(MemorySegment path, int datasync, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.fsyncdir(path.getUtf8String(0), datasync, new FileInfoImpl(fi, arena)); + return fuseOperations.fsyncdir(MemoryUtils.toUtf8StringOrNull(path), datasync, new FileInfoImpl(fi, arena)); } } private int getattr(MemorySegment path, MemorySegment stat, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.getattr(path.getUtf8String(0), new StatImpl(stat, arena), new FileInfoImpl(fi, arena)); + return fuseOperations.getattr(path.getUtf8String(0), new StatImpl(stat, arena), FileInfoImpl.of(fi, arena)); } } @@ -229,7 +230,7 @@ private int release(MemorySegment path, MemorySegment fi) { private int releasedir(MemorySegment path, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.releasedir(path.getUtf8String(0), new FileInfoImpl(fi, arena)); + return fuseOperations.releasedir(MemoryUtils.toUtf8StringOrNull(path), new FileInfoImpl(fi, arena)); } } @@ -253,7 +254,7 @@ private int symlink(MemorySegment linkname, MemorySegment target) { private int truncate(MemorySegment path, long size, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.truncate(path.getUtf8String(0), size, new FileInfoImpl(fi, arena)); + return fuseOperations.truncate(path.getUtf8String(0), size, FileInfoImpl.of(fi, arena)); } } @@ -270,11 +271,11 @@ int utimens(MemorySegment path, MemorySegment times, MemorySegment fi) { timespec.tv_sec$set(segment, 0); timespec.tv_nsec$set(segment, stat_h.UTIME_NOW()); var time = new TimeSpecImpl(segment); - return fuseOperations.utimens(path.getUtf8String(0), time, time, new FileInfoImpl(fi, arena)); + return fuseOperations.utimens(path.getUtf8String(0), time, time, FileInfoImpl.of(fi, arena)); } else { var time0 = times.asSlice(0, timespec.$LAYOUT().byteSize()); var time1 = times.asSlice(timespec.$LAYOUT().byteSize(), timespec.$LAYOUT().byteSize()); - return fuseOperations.utimens(path.getUtf8String(0), new TimeSpecImpl(time0), new TimeSpecImpl(time1), new FileInfoImpl(fi, arena)); + return fuseOperations.utimens(path.getUtf8String(0), new TimeSpecImpl(time0), new TimeSpecImpl(time1), FileInfoImpl.of(fi, arena)); } } } 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 5bf21d69..58232d94 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 @@ -4,6 +4,7 @@ import org.cryptomator.jfuse.api.FuseMount; import org.cryptomator.jfuse.api.FuseMountFailedException; import org.cryptomator.jfuse.api.FuseOperations; +import org.cryptomator.jfuse.api.util.MemoryUtils; import org.cryptomator.jfuse.mac.extr.fuse.fuse_args; import org.cryptomator.jfuse.mac.extr.fuse.fuse_h; import org.cryptomator.jfuse.mac.extr.fuse.fuse_operations; @@ -154,7 +155,7 @@ int fsync(MemorySegment path, int datasync, MemorySegment fi) { @VisibleForTesting int fsyncdir(MemorySegment path, int datasync, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.fsyncdir(path.getUtf8String(0), datasync, new FileInfoImpl(fi, arena)); + return fuseOperations.fsyncdir(MemoryUtils.toUtf8StringOrNull(path), datasync, new FileInfoImpl(fi, arena)); } } @@ -237,7 +238,7 @@ private int release(MemorySegment path, MemorySegment fi) { private int releasedir(MemorySegment path, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.releasedir(path.getUtf8String(0), new FileInfoImpl(fi, arena)); + return fuseOperations.releasedir(MemoryUtils.toUtf8StringOrNull(path), new FileInfoImpl(fi, arena)); } } diff --git a/jfuse-win/src/main/java/org/cryptomator/jfuse/win/FileInfoImpl.java b/jfuse-win/src/main/java/org/cryptomator/jfuse/win/FileInfoImpl.java index 33e9c17f..dd9f18bf 100644 --- a/jfuse-win/src/main/java/org/cryptomator/jfuse/win/FileInfoImpl.java +++ b/jfuse-win/src/main/java/org/cryptomator/jfuse/win/FileInfoImpl.java @@ -3,6 +3,7 @@ import org.cryptomator.jfuse.api.FileInfo; import org.cryptomator.jfuse.win.extr.fcntl.fcntl_h; import org.cryptomator.jfuse.win.extr.fuse3.fuse3_file_info; +import org.jetbrains.annotations.Nullable; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; @@ -20,6 +21,18 @@ record FileInfoImpl(MemorySegment segment) implements FileInfo { private static final int O_TRUNC = fcntl_h.O_TRUNC(); private static final int O_EXCL = fcntl_h.O_EXCL(); + /** + * Factory method to map native memory to an {@link FileInfo} object + * + * @param address the {@link MemorySegment} representing the starting address + * @param scope the {@link Arena} in which this object will be alive + * @return an {@link FileInfo} object or {@code null} if {@code address} is a NULL pointer + */ + @Nullable + public static FileInfoImpl of(MemorySegment address, Arena scope) { + return MemorySegment.NULL.equals(address) ? null : new FileInfoImpl(address, scope); + } + public FileInfoImpl(MemorySegment address, Arena scope) { this(fuse3_file_info.ofAddress(address, scope)); } 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 bce9878d..3a6f59b0 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 @@ -5,6 +5,7 @@ import org.cryptomator.jfuse.api.FuseMount; import org.cryptomator.jfuse.api.FuseMountFailedException; import org.cryptomator.jfuse.api.FuseOperations; +import org.cryptomator.jfuse.api.util.MemoryUtils; import org.cryptomator.jfuse.win.extr.fuse2.fuse2_h; import org.cryptomator.jfuse.win.extr.fuse2.fuse_args; import org.cryptomator.jfuse.win.extr.fuse3.fuse3_operations; @@ -125,14 +126,14 @@ MemorySegment init(MemorySegment conn, MemorySegment cfg) { private int chmod(MemorySegment path, int mode, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.chmod(path.getUtf8String(0), mode, new FileInfoImpl(fi, arena)); + return fuseOperations.chmod(path.getUtf8String(0), mode, FileInfoImpl.of(fi, arena)); } } @VisibleForTesting int chown(MemorySegment path, int uid, int gid, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.chown(path.getUtf8String(0), uid, gid, new FileInfoImpl(fi, arena)); + return fuseOperations.chown(path.getUtf8String(0), uid, gid, FileInfoImpl.of(fi, arena)); } } @@ -163,14 +164,14 @@ int fsync(MemorySegment path, int datasync, MemorySegment fi) { @VisibleForTesting int fsyncdir(MemorySegment path, int datasync, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.fsyncdir(path.getUtf8String(0), datasync, new FileInfoImpl(fi, arena)); + return fuseOperations.fsyncdir(MemoryUtils.toUtf8StringOrNull(path), datasync, new FileInfoImpl(fi, arena)); } } @VisibleForTesting int getattr(MemorySegment path, MemorySegment stat, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.getattr(path.getUtf8String(0), new StatImpl(stat, arena), new FileInfoImpl(fi, arena)); + return fuseOperations.getattr(path.getUtf8String(0), new StatImpl(stat, arena), FileInfoImpl.of(fi, arena)); } } @@ -239,7 +240,7 @@ private int release(MemorySegment path, MemorySegment fi) { private int releasedir(MemorySegment path, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.releasedir(path.getUtf8String(0), new FileInfoImpl(fi, arena)); + return fuseOperations.releasedir(MemoryUtils.toUtf8StringOrNull(path), new FileInfoImpl(fi, arena)); } } @@ -264,7 +265,7 @@ private int symlink(MemorySegment linkname, MemorySegment target) { @VisibleForTesting int truncate(MemorySegment path, long size, MemorySegment fi) { try (var arena = Arena.ofConfined()) { - return fuseOperations.truncate(path.getUtf8String(0), size, new FileInfoImpl(fi, arena)); + return fuseOperations.truncate(path.getUtf8String(0), size, FileInfoImpl.of(fi, arena)); } } @@ -281,7 +282,7 @@ int utimens(MemorySegment path, MemorySegment times, MemorySegment fi) { var segment = times.reinterpret(seq.byteSize()); var time0 = segment.asSlice(0, fuse_timespec.$LAYOUT().byteSize()); var time1 = segment.asSlice(fuse_timespec.$LAYOUT().byteSize(), fuse_timespec.$LAYOUT().byteSize()); - return fuseOperations.utimens(path.getUtf8String(0), new TimeSpecImpl(time0), new TimeSpecImpl(time1), new FileInfoImpl(fi, arena)); + return fuseOperations.utimens(path.getUtf8String(0), new TimeSpecImpl(time0), new TimeSpecImpl(time1), FileInfoImpl.of(fi, arena)); } }