diff --git a/README.md b/README.md index 5709f218..8a2878c1 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,10 @@ Not all [`fuse_operations`](https://libfuse.github.io/doxygen/structfuse__operat | flush | :white_check_mark: | | release | :white_check_mark: | | fsync | :white_check_mark: | -| setxattr | :x: | -| getxattr | :x: | -| listxattr | :x: | -| removexattr | :x: | +| setxattr | :white_check_mark: | +| getxattr | :white_check_mark: | +| listxattr | :white_check_mark: | +| removexattr | :white_check_mark: | | opendir | :white_check_mark: | | readdir | :white_check_mark: | | releasedir | :white_check_mark: | diff --git a/jfuse-api/pom.xml b/jfuse-api/pom.xml index a123e300..53e571d9 100644 --- a/jfuse-api/pom.xml +++ b/jfuse-api/pom.xml @@ -5,7 +5,7 @@ org.cryptomator jfuse-parent - 0.3.3 + 0.4.0 4.0.0 jfuse-api diff --git a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/Errno.java b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/Errno.java index 6fb9f786..dd62c590 100644 --- a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/Errno.java +++ b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/Errno.java @@ -88,6 +88,13 @@ public interface Errno { */ int enotempty(); + /** + * Operation not supported + * + * @return error constant {@code ENOTSUP} + */ + int enotsup(); + /** * Invalid argument * @@ -95,4 +102,26 @@ public interface Errno { */ int einval(); + /** + * Result too large + * + * @return error constant {@code ERANGE} + */ + int erange(); + + + /** + * No locks available + * + * @return error constant {@code ENOLCK} + */ + int enolck(); + + /** + * Filename too long + * + * @return error constant {@code ENAMETOOLONG} + */ + int enametoolong(); + } diff --git a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseOperations.java b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseOperations.java index d9686f79..4e534d55 100644 --- a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseOperations.java +++ b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseOperations.java @@ -23,7 +23,9 @@ enum Operation { FSYNC, FSYNCDIR, GET_ATTR, + GET_XATTR, INIT, + LIST_XATTR, MKDIR, OPEN, OPEN_DIR, @@ -32,8 +34,10 @@ enum Operation { READ_DIR, RELEASE, RELEASE_DIR, + REMOVE_XATTR, RENAME, RMDIR, + SET_XATTR, STATFS, SYMLINK, TRUNCATE, @@ -404,12 +408,11 @@ default int fsync(String path, int datasync, FileInfo fi) { * @param path file path * @param name attribute name * @param value attribute value - * @param size number of bytes of the value TODO: can this be combined with the value buffer? * @param flags defaults to zero, which creates or replaces the attribute. For fine-grained atomic control, * XATTR_CREATE or XATTR_REPLACE may be used. * @return 0 on success or negated error code (-errno) */ - default int setxattr(String path, String name, ByteBuffer value, long size, int flags) { + default int setxattr(String path, String name, ByteBuffer value, int flags) { return -errno().enosys(); } @@ -418,11 +421,10 @@ default int setxattr(String path, String name, ByteBuffer value, long size, int * * @param path file path * @param name attribute name - * @param value attribute value - * @param size size of the value buffer TODO: can this be combined with the value buffer? + * @param value attribute value. If buffer capacity is zero, return the size of the value * @return the non-negative value size or negated error code (-errno) */ - default int getxattr(String path, String name, ByteBuffer value, long size) { + default int getxattr(String path, String name, ByteBuffer value) { return -errno().enosys(); } @@ -430,11 +432,10 @@ default int getxattr(String path, String name, ByteBuffer value, long size) { * List extended attributes * * @param path file path - * @param list consecutive list of null-terminated attribute names (as many as fit into the buffer) - * @param size size of the list buffer TODO: can this be combined with the value buffer? - * @return the non-negative list size or negated error code (-errno) + * @param list consecutive list of null-terminated attribute names (as many as fit into the buffer). If buffer capacity is zero, return the size of the list + * @return the non-negative number of bytes written to the list buffer or negated error code (-errno) */ - default int listxattr(String path, ByteBuffer list, long size) { + default int listxattr(String path, ByteBuffer list) { return -errno().enosys(); } diff --git a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseOperationsDecorator.java b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseOperationsDecorator.java index cd6caf1b..a97eaeda 100644 --- a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseOperationsDecorator.java +++ b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/FuseOperationsDecorator.java @@ -32,6 +32,26 @@ default int getattr(String path, Stat stat, @Nullable FileInfo fi) { return delegate().getattr(path, stat, fi); } + @Override + default int getxattr(String path, String name, ByteBuffer value) { + return delegate().getxattr(path, name, value); + } + + @Override + default int setxattr(String path, String name, ByteBuffer value, int flags) { + return delegate().setxattr(path, name, value, flags); + } + + @Override + default int listxattr(String path, ByteBuffer list) { + return delegate().listxattr(path, list); + } + + @Override + default int removexattr(String path, String name) { + return delegate().removexattr(path, name); + } + @Override default int readlink(String path, ByteBuffer buf, long len) { return delegate().readlink(path, buf, len); diff --git a/jfuse-examples/pom.xml b/jfuse-examples/pom.xml index f107c22f..63c1d88b 100644 --- a/jfuse-examples/pom.xml +++ b/jfuse-examples/pom.xml @@ -5,7 +5,7 @@ org.cryptomator jfuse-parent - 0.3.3 + 0.4.0 4.0.0 jfuse-examples diff --git a/jfuse-examples/src/main/java/org/cryptomator/jfuse/examples/AbstractMirrorFileSystem.java b/jfuse-examples/src/main/java/org/cryptomator/jfuse/examples/AbstractMirrorFileSystem.java index ab809f22..9de512b6 100644 --- a/jfuse-examples/src/main/java/org/cryptomator/jfuse/examples/AbstractMirrorFileSystem.java +++ b/jfuse-examples/src/main/java/org/cryptomator/jfuse/examples/AbstractMirrorFileSystem.java @@ -38,6 +38,7 @@ import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; +import java.nio.file.attribute.UserDefinedFileAttributeView; import java.util.EnumSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -79,7 +80,9 @@ public Set supportedOperations() { FuseOperations.Operation.FSYNC, FuseOperations.Operation.FSYNCDIR, FuseOperations.Operation.GET_ATTR, + FuseOperations.Operation.GET_XATTR, FuseOperations.Operation.INIT, + FuseOperations.Operation.LIST_XATTR, FuseOperations.Operation.MKDIR, FuseOperations.Operation.OPEN_DIR, FuseOperations.Operation.READ_DIR, @@ -90,6 +93,8 @@ public Set supportedOperations() { FuseOperations.Operation.READ, FuseOperations.Operation.READLINK, FuseOperations.Operation.RELEASE, + FuseOperations.Operation.REMOVE_XATTR, + FuseOperations.Operation.SET_XATTR, FuseOperations.Operation.STATFS, FuseOperations.Operation.SYMLINK, FuseOperations.Operation.TRUNCATE, @@ -194,6 +199,96 @@ public int getattr(String path, Stat stat, FileInfo fi) { } } + @Override + public int getxattr(String path, String name, ByteBuffer value) { + LOG.trace("getxattr {} {}", path, name); + Path node = resolvePath(path); + try { + var xattr = Files.getFileAttributeView(node, UserDefinedFileAttributeView.class, LinkOption.NOFOLLOW_LINKS); + if (xattr == null) { + return -errno.enotsup(); + } + int size = xattr.size(name); + if (value.capacity() == 0) { + return size; + } else if (value.remaining() < size) { + return -errno.erange(); + } else { + return xattr.read(name, value); + } + } catch (NoSuchFileException e) { + return -errno.enoent(); + } catch (IOException e) { + return -errno.eio(); + } + } + + @Override + public int setxattr(String path, String name, ByteBuffer value, int flags) { + LOG.trace("setxattr {} {}", path, name); + Path node = resolvePath(path); + try { + var xattr = Files.getFileAttributeView(node, UserDefinedFileAttributeView.class, LinkOption.NOFOLLOW_LINKS); + if (xattr == null) { + return -errno.enotsup(); + } + xattr.write(name, value); + return 0; + } catch (NoSuchFileException e) { + return -errno.enoent(); + } catch (IOException e) { + return -errno.eio(); + } + } + + @Override + public int listxattr(String path, ByteBuffer list) { + LOG.trace("listxattr {}", path); + Path node = resolvePath(path); + try { + var xattr = Files.getFileAttributeView(node, UserDefinedFileAttributeView.class, LinkOption.NOFOLLOW_LINKS); + if (xattr == null) { + return -errno.enotsup(); + } + var names = xattr.list(); + if (list.capacity() == 0) { + var contentBytes = xattr.list().stream().map(StandardCharsets.UTF_8::encode).mapToInt(ByteBuffer::remaining).sum(); + var nulBytes = names.size(); + return contentBytes + nulBytes; // attr1\0aattr2\0attr3\0 + } else { + int startpos = list.position(); + for (var name : names) { + list.put(StandardCharsets.UTF_8.encode(name)).put((byte) 0x00); + } + return list.position() - startpos; + } + } catch (BufferOverflowException e) { + return -errno.erange(); + } catch (NoSuchFileException e) { + return -errno.enoent(); + } catch (IOException e) { + return -errno.eio(); + } + } + + @Override + public int removexattr(String path, String name) { + LOG.trace("removexattr {} {}", path, name); + Path node = resolvePath(path); + try { + var xattr = Files.getFileAttributeView(node, UserDefinedFileAttributeView.class, LinkOption.NOFOLLOW_LINKS); + if (xattr == null) { + return -errno.enotsup(); + } + xattr.delete(name); + return 0; + } catch (NoSuchFileException e) { + return -errno.enoent(); + } catch (IOException e) { + return -errno.eio(); + } + } + @SuppressWarnings("OctalInteger") protected void copyAttrsToStat(BasicFileAttributes attrs, Stat stat) { if (attrs instanceof PosixFileAttributes posixAttrs) { diff --git a/jfuse-linux-aarch64/pom.xml b/jfuse-linux-aarch64/pom.xml index acf929c1..72abb49b 100644 --- a/jfuse-linux-aarch64/pom.xml +++ b/jfuse-linux-aarch64/pom.xml @@ -5,7 +5,7 @@ jfuse-parent org.cryptomator - 0.3.3 + 0.4.0 4.0.0 jfuse-linux-aarch64 @@ -159,7 +159,11 @@ ENOTDIR EISDIR ENOTEMPTY + ENOTSUP EINVAL + ERANGE + ENOLCK + ENAMETOOLONG 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 929b20d4..6a359585 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 @@ -79,6 +79,8 @@ protected void bind(FuseOperations.Operation operation) { case FSYNC -> fuse_operations.fsync$set(fuseOperationsStruct, fuse_operations.fsync.allocate(this::fsync, fuseScope).address()); case FSYNCDIR -> fuse_operations.fsyncdir$set(fuseOperationsStruct, fuse_operations.fsyncdir.allocate(this::fsyncdir, fuseScope).address()); case GET_ATTR -> fuse_operations.getattr$set(fuseOperationsStruct, fuse_operations.getattr.allocate(this::getattr, fuseScope).address()); + case GET_XATTR -> fuse_operations.getxattr$set(fuseOperationsStruct, fuse_operations.getxattr.allocate(this::getxattr, fuseScope).address()); + case LIST_XATTR -> fuse_operations.listxattr$set(fuseOperationsStruct, fuse_operations.listxattr.allocate(this::listxattr, fuseScope).address()); case MKDIR -> fuse_operations.mkdir$set(fuseOperationsStruct, fuse_operations.mkdir.allocate(this::mkdir, fuseScope).address()); case OPEN -> fuse_operations.open$set(fuseOperationsStruct, fuse_operations.open.allocate(this::open, fuseScope).address()); case OPEN_DIR -> fuse_operations.opendir$set(fuseOperationsStruct, fuse_operations.opendir.allocate(this::opendir, fuseScope).address()); @@ -87,8 +89,10 @@ protected void bind(FuseOperations.Operation operation) { case READLINK -> fuse_operations.readlink$set(fuseOperationsStruct, fuse_operations.readlink.allocate(this::readlink, fuseScope).address()); case RELEASE -> fuse_operations.release$set(fuseOperationsStruct, fuse_operations.release.allocate(this::release, fuseScope).address()); case RELEASE_DIR -> fuse_operations.releasedir$set(fuseOperationsStruct, fuse_operations.releasedir.allocate(this::releasedir, fuseScope).address()); + case REMOVE_XATTR -> fuse_operations.removexattr$set(fuseOperationsStruct, fuse_operations.removexattr.allocate(this::removexattr, fuseScope).address()); case RENAME -> fuse_operations.rename$set(fuseOperationsStruct, fuse_operations.rename.allocate(this::rename, fuseScope).address()); case RMDIR -> fuse_operations.rmdir$set(fuseOperationsStruct, fuse_operations.rmdir.allocate(this::rmdir, fuseScope).address()); + case SET_XATTR -> fuse_operations.setxattr$set(fuseOperationsStruct, fuse_operations.setxattr.allocate(this::setxattr, fuseScope).address()); case STATFS -> fuse_operations.statfs$set(fuseOperationsStruct, fuse_operations.statfs.allocate(this::statfs, fuseScope).address()); case SYMLINK -> fuse_operations.symlink$set(fuseOperationsStruct, fuse_operations.symlink.allocate(this::symlink, fuseScope).address()); case TRUNCATE -> fuse_operations.truncate$set(fuseOperationsStruct, fuse_operations.truncate.allocate(this::truncate, fuseScope).address()); @@ -163,6 +167,32 @@ private int getattr(MemoryAddress path, MemoryAddress stat, MemoryAddress fi) { } } + @VisibleForTesting + int getxattr(MemoryAddress path, MemoryAddress name, MemoryAddress value, long size) { + try (var scope = MemorySession.openConfined()) { + return fuseOperations.getxattr(path.getUtf8String(0), name.getUtf8String(0), MemorySegment.ofAddress(value.address(), size, scope).asByteBuffer()); + } + } + + @VisibleForTesting + int setxattr(MemoryAddress path, MemoryAddress name, MemoryAddress value, long size, int flags) { + try (var scope = MemorySession.openConfined()) { + return fuseOperations.setxattr(path.getUtf8String(0), name.getUtf8String(0), MemorySegment.ofAddress(value.address(), size, scope).asByteBuffer(), flags); + } + } + + @VisibleForTesting + int listxattr(MemoryAddress path, MemoryAddress value, long size) { + try (var scope = MemorySession.openConfined()) { + return fuseOperations.listxattr(path.getUtf8String(0), MemorySegment.ofAddress(value.address(), size, scope).asByteBuffer()); + } + } + + @VisibleForTesting + int removexattr(MemoryAddress path, MemoryAddress name) { + return fuseOperations.removexattr(path.getUtf8String(0), name.getUtf8String(0)); + } + private int mkdir(MemoryAddress path, int mode) { return fuseOperations.mkdir(path.getUtf8String(0), mode); } diff --git a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/LinuxErrno.java b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/LinuxErrno.java index 6b936955..8470da28 100644 --- a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/LinuxErrno.java +++ b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/LinuxErrno.java @@ -60,8 +60,28 @@ public int enotempty() { return errno_h.ENOTEMPTY(); } + @Override + public int enotsup() { + return errno_h.ENOTSUP(); + } + @Override public int einval() { return errno_h.EINVAL(); } + + @Override + public int erange() { + return errno_h.ERANGE(); + } + + @Override + public int enolck() { + return errno_h.ENOLCK(); + } + + @Override + public int enametoolong() { + return errno_h.ENAMETOOLONG(); + } } diff --git a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/extr/errno_h.java b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/extr/errno_h.java index e7ff1f2e..844b540b 100644 --- a/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/extr/errno_h.java +++ b/jfuse-linux-aarch64/src/main/java/org/cryptomator/jfuse/linux/aarch64/extr/errno_h.java @@ -48,12 +48,24 @@ public static int EINVAL() { public static int EROFS() { return (int)30L; } + public static int ERANGE() { + return (int)34L; + } + public static int ENAMETOOLONG() { + return (int)36L; + } + public static int ENOLCK() { + return (int)37L; + } public static int ENOSYS() { return (int)38L; } public static int ENOTEMPTY() { return (int)39L; } + public static int ENOTSUP() { + return (int)95L; + } } 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 4d1f3ea2..5494ea6d 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 @@ -234,6 +234,74 @@ public void testUtimens(long sec0, long nsec0, long sec1, long nsec1) { } } + @Nested + @DisplayName("attr") + public class Attr { + + @Test + @DisplayName("getxattr") + public void testGetxattr() { + try (var scope = MemorySession.openConfined()) { + var path = scope.allocateUtf8String("/foo"); + var name = scope.allocateUtf8String("bar"); + var value = scope.allocate(100); + + Mockito.doReturn(42).when(fuseOps).getxattr(Mockito.eq("/foo"), Mockito.eq("bar"), Mockito.any()); + + var result = fuseImpl.getxattr(path.address(), name.address(), value.address(), 100); + + Assertions.assertEquals(42, result); + } + } + + @Test + @DisplayName("setxattr") + public void testSetxattr() { + try (var scope = MemorySession.openConfined()) { + var path = scope.allocateUtf8String("/foo"); + var name = scope.allocateUtf8String("bar"); + var value = scope.allocate(100); + + Mockito.doReturn(42).when(fuseOps).setxattr(Mockito.eq("/foo"), Mockito.eq("bar"), Mockito.any(), Mockito.anyInt()); + + var result = fuseImpl.setxattr(path.address(), name.address(), value.address(), 100, 0xDEADBEEF); + + Assertions.assertEquals(42, result); + } + } + + @Test + @DisplayName("listxattr") + public void testListxattr() { + try (var scope = MemorySession.openConfined()) { + var path = scope.allocateUtf8String("/foo"); + var list = scope.allocate(100); + + Mockito.doReturn(42).when(fuseOps).listxattr(Mockito.eq("/foo"), Mockito.any()); + + var result = fuseImpl.listxattr(path.address(), list.address(), 100); + + Assertions.assertEquals(42, result); + } + } + + @Test + @DisplayName("removexattr") + public void testRemovexattr() { + try (var scope = MemorySession.openConfined()) { + var path = scope.allocateUtf8String("/foo"); + var name = scope.allocateUtf8String("bar"); + + Mockito.doReturn(42).when(fuseOps).removexattr(Mockito.eq("/foo"), Mockito.eq("bar")); + + var result = fuseImpl.removexattr(path.address(), name.address()); + + Assertions.assertEquals(42, result); + } + } + + } + @Test @DisplayName("chown") public void testChown() { diff --git a/jfuse-linux-amd64/pom.xml b/jfuse-linux-amd64/pom.xml index 456ff258..4a010077 100644 --- a/jfuse-linux-amd64/pom.xml +++ b/jfuse-linux-amd64/pom.xml @@ -5,7 +5,7 @@ org.cryptomator jfuse-parent - 0.3.3 + 0.4.0 4.0.0 jfuse-linux-amd64 @@ -159,7 +159,11 @@ ENOTDIR EISDIR ENOTEMPTY + ENOTSUP EINVAL + ERANGE + ENOLCK + ENAMETOOLONG 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 500ac7aa..59977bad 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 @@ -79,6 +79,8 @@ protected void bind(FuseOperations.Operation operation) { case FSYNC -> fuse_operations.fsync$set(fuseOperationsStruct, fuse_operations.fsync.allocate(this::fsync, fuseScope).address()); case FSYNCDIR -> fuse_operations.fsyncdir$set(fuseOperationsStruct, fuse_operations.fsyncdir.allocate(this::fsyncdir, fuseScope).address()); case GET_ATTR -> fuse_operations.getattr$set(fuseOperationsStruct, fuse_operations.getattr.allocate(this::getattr, fuseScope).address()); + case GET_XATTR -> fuse_operations.getxattr$set(fuseOperationsStruct, fuse_operations.getxattr.allocate(this::getxattr, fuseScope).address()); + case LIST_XATTR -> fuse_operations.listxattr$set(fuseOperationsStruct, fuse_operations.listxattr.allocate(this::listxattr, fuseScope).address()); case MKDIR -> fuse_operations.mkdir$set(fuseOperationsStruct, fuse_operations.mkdir.allocate(this::mkdir, fuseScope).address()); case OPEN -> fuse_operations.open$set(fuseOperationsStruct, fuse_operations.open.allocate(this::open, fuseScope).address()); case OPEN_DIR -> fuse_operations.opendir$set(fuseOperationsStruct, fuse_operations.opendir.allocate(this::opendir, fuseScope).address()); @@ -87,8 +89,10 @@ protected void bind(FuseOperations.Operation operation) { case READLINK -> fuse_operations.readlink$set(fuseOperationsStruct, fuse_operations.readlink.allocate(this::readlink, fuseScope).address()); case RELEASE -> fuse_operations.release$set(fuseOperationsStruct, fuse_operations.release.allocate(this::release, fuseScope).address()); case RELEASE_DIR -> fuse_operations.releasedir$set(fuseOperationsStruct, fuse_operations.releasedir.allocate(this::releasedir, fuseScope).address()); + case REMOVE_XATTR -> fuse_operations.removexattr$set(fuseOperationsStruct, fuse_operations.removexattr.allocate(this::removexattr, fuseScope).address()); case RENAME -> fuse_operations.rename$set(fuseOperationsStruct, fuse_operations.rename.allocate(this::rename, fuseScope).address()); case RMDIR -> fuse_operations.rmdir$set(fuseOperationsStruct, fuse_operations.rmdir.allocate(this::rmdir, fuseScope).address()); + case SET_XATTR -> fuse_operations.setxattr$set(fuseOperationsStruct, fuse_operations.setxattr.allocate(this::setxattr, fuseScope).address()); case STATFS -> fuse_operations.statfs$set(fuseOperationsStruct, fuse_operations.statfs.allocate(this::statfs, fuseScope).address()); case SYMLINK -> fuse_operations.symlink$set(fuseOperationsStruct, fuse_operations.symlink.allocate(this::symlink, fuseScope).address()); case TRUNCATE -> fuse_operations.truncate$set(fuseOperationsStruct, fuse_operations.truncate.allocate(this::truncate, fuseScope).address()); @@ -163,6 +167,32 @@ private int getattr(MemoryAddress path, MemoryAddress stat, MemoryAddress fi) { } } + @VisibleForTesting + int getxattr(MemoryAddress path, MemoryAddress name, MemoryAddress value, long size) { + try (var scope = MemorySession.openConfined()) { + return fuseOperations.getxattr(path.getUtf8String(0), name.getUtf8String(0), MemorySegment.ofAddress(value.address(), size, scope).asByteBuffer()); + } + } + + @VisibleForTesting + int setxattr(MemoryAddress path, MemoryAddress name, MemoryAddress value, long size, int flags) { + try (var scope = MemorySession.openConfined()) { + return fuseOperations.setxattr(path.getUtf8String(0), name.getUtf8String(0), MemorySegment.ofAddress(value.address(), size, scope).asByteBuffer(), flags); + } + } + + @VisibleForTesting + int listxattr(MemoryAddress path, MemoryAddress value, long size) { + try (var scope = MemorySession.openConfined()) { + return fuseOperations.listxattr(path.getUtf8String(0), MemorySegment.ofAddress(value.address(), size, scope).asByteBuffer()); + } + } + + @VisibleForTesting + int removexattr(MemoryAddress path, MemoryAddress name) { + return fuseOperations.removexattr(path.getUtf8String(0), name.getUtf8String(0)); + } + private int mkdir(MemoryAddress path, int mode) { return fuseOperations.mkdir(path.getUtf8String(0), mode); } diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/LinuxErrno.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/LinuxErrno.java index 7495a737..643bd01d 100644 --- a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/LinuxErrno.java +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/LinuxErrno.java @@ -60,8 +60,28 @@ public int enotempty() { return errno_h.ENOTEMPTY(); } + @Override + public int enotsup() { + return errno_h.ENOTSUP(); + } + @Override public int einval() { return errno_h.EINVAL(); } + + @Override + public int erange() { + return errno_h.ERANGE(); + } + + @Override + public int enolck() { + return errno_h.ENOLCK(); + } + + @Override + public int enametoolong() { + return errno_h.ENAMETOOLONG(); + } } diff --git a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/extr/errno_h.java b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/extr/errno_h.java index 14555329..5e1ad38c 100644 --- a/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/extr/errno_h.java +++ b/jfuse-linux-amd64/src/main/java/org/cryptomator/jfuse/linux/amd64/extr/errno_h.java @@ -48,12 +48,24 @@ public static int EINVAL() { public static int EROFS() { return (int)30L; } + public static int ERANGE() { + return (int)34L; + } + public static int ENAMETOOLONG() { + return (int)36L; + } + public static int ENOLCK() { + return (int)37L; + } public static int ENOSYS() { return (int)38L; } public static int ENOTEMPTY() { return (int)39L; } + public static int ENOTSUP() { + return (int)95L; + } } 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 c2ed724d..7d377ab0 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 @@ -234,6 +234,74 @@ public void testUtimens(long sec0, long nsec0, long sec1, long nsec1) { } } + @Nested + @DisplayName("attr") + public class Attr { + + @Test + @DisplayName("getxattr") + public void testGetxattr() { + try (var scope = MemorySession.openConfined()) { + var path = scope.allocateUtf8String("/foo"); + var name = scope.allocateUtf8String("bar"); + var value = scope.allocate(100); + + Mockito.doReturn(42).when(fuseOps).getxattr(Mockito.eq("/foo"), Mockito.eq("bar"), Mockito.any()); + + var result = fuseImpl.getxattr(path.address(), name.address(), value.address(), 100); + + Assertions.assertEquals(42, result); + } + } + + @Test + @DisplayName("setxattr") + public void testSetxattr() { + try (var scope = MemorySession.openConfined()) { + var path = scope.allocateUtf8String("/foo"); + var name = scope.allocateUtf8String("bar"); + var value = scope.allocate(100); + + Mockito.doReturn(42).when(fuseOps).setxattr(Mockito.eq("/foo"), Mockito.eq("bar"), Mockito.any(), Mockito.anyInt()); + + var result = fuseImpl.setxattr(path.address(), name.address(), value.address(), 100, 0xDEADBEEF); + + Assertions.assertEquals(42, result); + } + } + + @Test + @DisplayName("listxattr") + public void testListxattr() { + try (var scope = MemorySession.openConfined()) { + var path = scope.allocateUtf8String("/foo"); + var list = scope.allocate(100); + + Mockito.doReturn(42).when(fuseOps).listxattr(Mockito.eq("/foo"), Mockito.any()); + + var result = fuseImpl.listxattr(path.address(), list.address(), 100); + + Assertions.assertEquals(42, result); + } + } + + @Test + @DisplayName("removexattr") + public void testRemovexattr() { + try (var scope = MemorySession.openConfined()) { + var path = scope.allocateUtf8String("/foo"); + var name = scope.allocateUtf8String("bar"); + + Mockito.doReturn(42).when(fuseOps).removexattr(Mockito.eq("/foo"), Mockito.eq("bar")); + + var result = fuseImpl.removexattr(path.address(), name.address()); + + Assertions.assertEquals(42, result); + } + } + + } + @Test @DisplayName("chown") public void testChown() { diff --git a/jfuse-mac/pom.xml b/jfuse-mac/pom.xml index c9671817..df6ac927 100644 --- a/jfuse-mac/pom.xml +++ b/jfuse-mac/pom.xml @@ -5,7 +5,7 @@ org.cryptomator jfuse-parent - 0.3.3 + 0.4.0 4.0.0 jfuse-mac @@ -133,7 +133,11 @@ ENOTDIR EISDIR ENOTEMPTY + ENOTSUP EINVAL + ERANGE + ENOLCK + ENAMETOOLONG 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 ff0b6379..f88362f0 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 @@ -84,6 +84,8 @@ protected void bind(FuseOperations.Operation operation) { fuse_operations.getattr$set(fuseOperationsStruct, fuse_operations.getattr.allocate(this::getattr, fuseScope).address()); fuse_operations.fgetattr$set(fuseOperationsStruct, fuse_operations.fgetattr.allocate(this::fgetattr, fuseScope).address()); } + case GET_XATTR -> fuse_operations.getxattr$set(fuseOperationsStruct, fuse_operations.getxattr.allocate(this::getxattr, fuseScope).address()); + case LIST_XATTR -> fuse_operations.listxattr$set(fuseOperationsStruct, fuse_operations.listxattr.allocate(this::listxattr, fuseScope).address()); case MKDIR -> fuse_operations.mkdir$set(fuseOperationsStruct, fuse_operations.mkdir.allocate(this::mkdir, fuseScope).address()); case OPEN -> fuse_operations.open$set(fuseOperationsStruct, fuse_operations.open.allocate(this::open, fuseScope).address()); case OPEN_DIR -> fuse_operations.opendir$set(fuseOperationsStruct, fuse_operations.opendir.allocate(this::opendir, fuseScope).address()); @@ -92,8 +94,10 @@ protected void bind(FuseOperations.Operation operation) { case READLINK -> fuse_operations.readlink$set(fuseOperationsStruct, fuse_operations.readlink.allocate(this::readlink, fuseScope).address()); case RELEASE -> fuse_operations.release$set(fuseOperationsStruct, fuse_operations.release.allocate(this::release, fuseScope).address()); case RELEASE_DIR -> fuse_operations.releasedir$set(fuseOperationsStruct, fuse_operations.releasedir.allocate(this::releasedir, fuseScope).address()); + case REMOVE_XATTR -> fuse_operations.removexattr$set(fuseOperationsStruct, fuse_operations.removexattr.allocate(this::removexattr, fuseScope).address()); case RENAME -> fuse_operations.rename$set(fuseOperationsStruct, fuse_operations.rename.allocate(this::rename, fuseScope).address()); case RMDIR -> fuse_operations.rmdir$set(fuseOperationsStruct, fuse_operations.rmdir.allocate(this::rmdir, fuseScope).address()); + case SET_XATTR -> fuse_operations.setxattr$set(fuseOperationsStruct, fuse_operations.setxattr.allocate(this::setxattr, fuseScope).address()); case STATFS -> fuse_operations.statfs$set(fuseOperationsStruct, fuse_operations.statfs.allocate(this::statfs, fuseScope).address()); case SYMLINK -> fuse_operations.symlink$set(fuseOperationsStruct, fuse_operations.symlink.allocate(this::symlink, fuseScope).address()); case TRUNCATE -> { @@ -171,6 +175,32 @@ int fgetattr(MemoryAddress path, MemoryAddress stat, MemoryAddress fi) { } } + @VisibleForTesting + int getxattr(MemoryAddress path, MemoryAddress name, MemoryAddress value, long size) { + try (var scope = MemorySession.openConfined()) { + return fuseOperations.getxattr(path.getUtf8String(0), name.getUtf8String(0), MemorySegment.ofAddress(value.address(), size, scope).asByteBuffer()); + } + } + + @VisibleForTesting + int setxattr(MemoryAddress path, MemoryAddress name, MemoryAddress value, long size, int flags) { + try (var scope = MemorySession.openConfined()) { + return fuseOperations.setxattr(path.getUtf8String(0), name.getUtf8String(0), MemorySegment.ofAddress(value.address(), size, scope).asByteBuffer(), flags); + } + } + + @VisibleForTesting + int listxattr(MemoryAddress path, MemoryAddress value, long size) { + try (var scope = MemorySession.openConfined()) { + return fuseOperations.listxattr(path.getUtf8String(0), MemorySegment.ofAddress(value.address(), size, scope).asByteBuffer()); + } + } + + @VisibleForTesting + int removexattr(MemoryAddress path, MemoryAddress name) { + return fuseOperations.removexattr(path.getUtf8String(0), name.getUtf8String(0)); + } + private int mkdir(MemoryAddress path, short mode) { return fuseOperations.mkdir(path.getUtf8String(0), mode); } diff --git a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/MacErrno.java b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/MacErrno.java index 4abde34c..69b9aa28 100644 --- a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/MacErrno.java +++ b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/MacErrno.java @@ -60,8 +60,28 @@ public int enotempty() { return errno_h.ENOTEMPTY(); } + @Override + public int enotsup() { + return errno_h.ENOTSUP(); + } + @Override public int einval() { return errno_h.EINVAL(); } + + @Override + public int erange() { + return errno_h.ERANGE(); + } + + @Override + public int enolck() { + return errno_h.ENOLCK(); + } + + @Override + public int enametoolong() { + return errno_h.ENAMETOOLONG(); + } } diff --git a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/extr/errno_h.java b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/extr/errno_h.java index 0aa9fed6..b3b007e1 100644 --- a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/extr/errno_h.java +++ b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/extr/errno_h.java @@ -48,9 +48,21 @@ public static int EINVAL() { public static int EROFS() { return (int)30L; } + public static int ERANGE() { + return (int)34L; + } + public static int ENOTSUP() { + return (int)45L; + } + public static int ENAMETOOLONG() { + return (int)63L; + } public static int ENOTEMPTY() { return (int)66L; } + public static int ENOLCK() { + return (int)77L; + } public static int ENOSYS() { return (int)78L; } diff --git a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/extr/stat_h.java b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/extr/stat_h.java index 25601f9e..16d83ef8 100644 --- a/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/extr/stat_h.java +++ b/jfuse-mac/src/main/java/org/cryptomator/jfuse/mac/extr/stat_h.java @@ -18,15 +18,6 @@ public class stat_h { public static OfFloat C_FLOAT = Constants$root.C_FLOAT$LAYOUT; public static OfDouble C_DOUBLE = Constants$root.C_DOUBLE$LAYOUT; public static OfAddress C_POINTER = Constants$root.C_POINTER$LAYOUT; - public static int S_IFDIR() { - return (int)16384L; - } - public static int S_IFREG() { - return (int)32768L; - } - public static int S_IFLNK() { - return (int)40960L; - } public static int UTIME_NOW() { return (int)-1L; } 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 875bf928..dd7b1daf 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 @@ -193,8 +193,8 @@ public void testUtimens(long sec0, long nsec0, long sec1, long nsec1) { } @Nested - @DisplayName("getattr") - public class GetAttr { + @DisplayName("attr") + public class Attr { @Test @DisplayName("getattr") @@ -225,6 +225,68 @@ public void testFgetattr() { } } + @Test + @DisplayName("getxattr") + public void testGetxattr() { + try (var scope = MemorySession.openConfined()) { + var path = scope.allocateUtf8String("/foo"); + var name = scope.allocateUtf8String("bar"); + var value = scope.allocate(100); + + Mockito.doReturn(42).when(fuseOps).getxattr(Mockito.eq("/foo"), Mockito.eq("bar"), Mockito.any()); + + var result = fuseImpl.getxattr(path.address(), name.address(), value.address(), 100); + + Assertions.assertEquals(42, result); + } + } + + @Test + @DisplayName("setxattr") + public void testSetxattr() { + try (var scope = MemorySession.openConfined()) { + var path = scope.allocateUtf8String("/foo"); + var name = scope.allocateUtf8String("bar"); + var value = scope.allocate(100); + + Mockito.doReturn(42).when(fuseOps).setxattr(Mockito.eq("/foo"), Mockito.eq("bar"), Mockito.any(), Mockito.anyInt()); + + var result = fuseImpl.setxattr(path.address(), name.address(), value.address(), 100, 0xDEADBEEF); + + Assertions.assertEquals(42, result); + } + } + + @Test + @DisplayName("listxattr") + public void testListxattr() { + try (var scope = MemorySession.openConfined()) { + var path = scope.allocateUtf8String("/foo"); + var list = scope.allocate(100); + + Mockito.doReturn(42).when(fuseOps).listxattr(Mockito.eq("/foo"), Mockito.any()); + + var result = fuseImpl.listxattr(path.address(), list.address(), 100); + + Assertions.assertEquals(42, result); + } + } + + @Test + @DisplayName("removexattr") + public void testRemovexattr() { + try (var scope = MemorySession.openConfined()) { + var path = scope.allocateUtf8String("/foo"); + var name = scope.allocateUtf8String("bar"); + + Mockito.doReturn(42).when(fuseOps).removexattr(Mockito.eq("/foo"), Mockito.eq("bar")); + + var result = fuseImpl.removexattr(path.address(), name.address()); + + Assertions.assertEquals(42, result); + } + } + } @Nested diff --git a/jfuse-tests/pom.xml b/jfuse-tests/pom.xml index f09be207..5acb0b07 100644 --- a/jfuse-tests/pom.xml +++ b/jfuse-tests/pom.xml @@ -5,7 +5,7 @@ org.cryptomator jfuse-parent - 0.3.3 + 0.4.0 4.0.0 jfuse-tests diff --git a/jfuse-tests/src/test/java/org/cryptomator/jfuse/tests/MirrorIT.java b/jfuse-tests/src/test/java/org/cryptomator/jfuse/tests/MirrorIT.java index 8c03db08..4b5b80d0 100644 --- a/jfuse-tests/src/test/java/org/cryptomator/jfuse/tests/MirrorIT.java +++ b/jfuse-tests/src/test/java/org/cryptomator/jfuse/tests/MirrorIT.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.io.TempDir; @@ -24,14 +25,18 @@ import org.junit.jupiter.params.provider.ValueSource; import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.UserDefinedFileAttributeView; import java.time.Duration; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.concurrent.TimeUnit; @EnabledIf("hasSupportedImplementation") @@ -200,4 +205,72 @@ public void testReadFiles(String filename) throws IOException { } + @Nested + @DisabledOnOs(OS.WINDOWS) // see remark on https://github.com/cryptomator/jfuse/pull/26 + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + @DisplayName("Extended Attributes") + public class TestXattr { + + private Path file; + + @BeforeAll + public void setup() throws IOException { + file = mirror.resolve("xattr.txt"); + Files.createFile(file); + } + + @Order(1) + @DisplayName("setxattr /xattr.txt") + @ParameterizedTest(name = "{0}") + @ValueSource(strings = {"attr1", "attr2", "attr3", "attr4", "attr5"}) + public void testSetxattr(String attrName) { + var attrView = Files.getFileAttributeView(file, UserDefinedFileAttributeView.class); + var attrValue = StandardCharsets.UTF_8.encode(attrName); + + Assertions.assertDoesNotThrow(() -> attrView.write(attrName, attrValue)); + } + + @Order(2) + @Test + @DisplayName("removexattr /xattr.txt") + + public void testRemovexattr() { + var attrView = Files.getFileAttributeView(file, UserDefinedFileAttributeView.class); + + Assertions.assertDoesNotThrow(() -> attrView.delete("attr3")); + } + + @Order(3) + @Test + @DisplayName("listxattr /xattr.txt") + public void testListxattr() throws IOException { + var attrView = Files.getFileAttributeView(file, UserDefinedFileAttributeView.class); + var result = attrView.list(); + + Assertions.assertAll( + () -> Assertions.assertTrue(result.contains("attr1")), + () -> Assertions.assertTrue(result.contains("attr2")), + () -> Assertions.assertFalse(result.contains("attr3")), + () -> Assertions.assertTrue(result.contains("attr4")), + () -> Assertions.assertTrue(result.contains("attr5")) + ); + } + + @Order(4) + @DisplayName("getxattr") + @ParameterizedTest(name = "{0}") + @ValueSource(strings = {/* "attr1", BUG in fuse-t */ "attr2", "attr4", "attr5"}) + public void testGetxattr(String attrName) throws IOException { + var attrView = Files.getFileAttributeView(file, UserDefinedFileAttributeView.class); + var buffer = ByteBuffer.allocate(attrView.size(attrName)); + var read = attrView.read(attrName, buffer); + buffer.flip(); + var value = StandardCharsets.UTF_8.decode(buffer).toString(); + + Assertions.assertEquals(attrName.length(), read); + Assertions.assertEquals(attrName, value); + } + + } } diff --git a/jfuse-win/pom.xml b/jfuse-win/pom.xml index f475c73d..9ba8e5b3 100644 --- a/jfuse-win/pom.xml +++ b/jfuse-win/pom.xml @@ -5,7 +5,7 @@ org.cryptomator jfuse-parent - 0.3.3 + 0.4.0 4.0.0 jfuse-win @@ -153,7 +153,11 @@ ENOTDIR EISDIR ENOTEMPTY + ENOTSUP EINVAL + ERANGE + ENOLCK + ENAMETOOLONG 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 43b745e9..166860c9 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 @@ -91,6 +91,8 @@ protected void bind(FuseOperations.Operation operation) { case FSYNC -> fuse3_operations.fsync$set(fuseOperationsStruct, fuse3_operations.fsync.allocate(this::fsync, fuseScope).address()); case FSYNCDIR -> fuse3_operations.fsyncdir$set(fuseOperationsStruct, fuse3_operations.fsyncdir.allocate(this::fsyncdir, fuseScope).address()); case GET_ATTR -> fuse3_operations.getattr$set(fuseOperationsStruct, fuse3_operations.getattr.allocate(this::getattr, fuseScope).address()); + case GET_XATTR -> fuse3_operations.getxattr$set(fuseOperationsStruct, fuse3_operations.getxattr.allocate(this::getxattr, fuseScope).address()); + case LIST_XATTR -> fuse3_operations.listxattr$set(fuseOperationsStruct, fuse3_operations.listxattr.allocate(this::listxattr, fuseScope).address()); case MKDIR -> fuse3_operations.mkdir$set(fuseOperationsStruct, fuse3_operations.mkdir.allocate(this::mkdir, fuseScope).address()); case OPEN -> fuse3_operations.open$set(fuseOperationsStruct, fuse3_operations.open.allocate(this::open, fuseScope).address()); case OPEN_DIR -> fuse3_operations.opendir$set(fuseOperationsStruct, fuse3_operations.opendir.allocate(this::opendir, fuseScope).address()); @@ -99,8 +101,10 @@ protected void bind(FuseOperations.Operation operation) { case READLINK -> fuse3_operations.readlink$set(fuseOperationsStruct, fuse3_operations.readlink.allocate(this::readlink, fuseScope).address()); case RELEASE -> fuse3_operations.release$set(fuseOperationsStruct, fuse3_operations.release.allocate(this::release, fuseScope).address()); case RELEASE_DIR -> fuse3_operations.releasedir$set(fuseOperationsStruct, fuse3_operations.releasedir.allocate(this::releasedir, fuseScope).address()); + case REMOVE_XATTR -> fuse3_operations.removexattr$set(fuseOperationsStruct, fuse3_operations.removexattr.allocate(this::removexattr, fuseScope).address()); case RENAME -> fuse3_operations.rename$set(fuseOperationsStruct, fuse3_operations.rename.allocate(this::rename, fuseScope).address()); case RMDIR -> fuse3_operations.rmdir$set(fuseOperationsStruct, fuse3_operations.rmdir.allocate(this::rmdir, fuseScope).address()); + case SET_XATTR -> fuse3_operations.setxattr$set(fuseOperationsStruct, fuse3_operations.setxattr.allocate(this::setxattr, fuseScope).address()); case STATFS -> fuse3_operations.statfs$set(fuseOperationsStruct, fuse3_operations.statfs.allocate(this::statfs, fuseScope).address()); case SYMLINK -> fuse3_operations.symlink$set(fuseOperationsStruct, fuse3_operations.symlink.allocate(this::symlink, fuseScope).address()); case TRUNCATE -> fuse3_operations.truncate$set(fuseOperationsStruct, fuse3_operations.truncate.allocate(this::truncate, fuseScope).address()); @@ -172,6 +176,32 @@ int getattr(MemoryAddress path, MemoryAddress stat, MemoryAddress fi) { } } + @VisibleForTesting + int getxattr(MemoryAddress path, MemoryAddress name, MemoryAddress value, long size) { + try (var scope = MemorySession.openConfined()) { + return fuseOperations.getxattr(path.getUtf8String(0), name.getUtf8String(0), MemorySegment.ofAddress(value.address(), size, scope).asByteBuffer()); + } + } + + @VisibleForTesting + int setxattr(MemoryAddress path, MemoryAddress name, MemoryAddress value, long size, int flags) { + try (var scope = MemorySession.openConfined()) { + return fuseOperations.setxattr(path.getUtf8String(0), name.getUtf8String(0), MemorySegment.ofAddress(value.address(), size, scope).asByteBuffer(), flags); + } + } + + @VisibleForTesting + int listxattr(MemoryAddress path, MemoryAddress value, long size) { + try (var scope = MemorySession.openConfined()) { + return fuseOperations.listxattr(path.getUtf8String(0), MemorySegment.ofAddress(value.address(), size, scope).asByteBuffer()); + } + } + + @VisibleForTesting + int removexattr(MemoryAddress path, MemoryAddress name) { + return fuseOperations.removexattr(path.getUtf8String(0), name.getUtf8String(0)); + } + private int mkdir(MemoryAddress path, int mode) { return fuseOperations.mkdir(path.getUtf8String(0), mode); } diff --git a/jfuse-win/src/main/java/org/cryptomator/jfuse/win/WinErrno.java b/jfuse-win/src/main/java/org/cryptomator/jfuse/win/WinErrno.java index b9cd9632..f9e026d4 100644 --- a/jfuse-win/src/main/java/org/cryptomator/jfuse/win/WinErrno.java +++ b/jfuse-win/src/main/java/org/cryptomator/jfuse/win/WinErrno.java @@ -60,8 +60,28 @@ public int enotempty() { return errno_h.ENOTEMPTY(); } + @Override + public int enotsup() { + return errno_h.ENOTSUP(); + } + @Override public int einval() { return errno_h.EINVAL(); } + + @Override + public int erange() { + return errno_h.ERANGE(); + } + + @Override + public int enolck() { + return errno_h.ENOLCK(); + } + + @Override + public int enametoolong() { + return errno_h.ENAMETOOLONG(); + } } diff --git a/jfuse-win/src/main/java/org/cryptomator/jfuse/win/extr/errno_h.java b/jfuse-win/src/main/java/org/cryptomator/jfuse/win/extr/errno_h.java index e2d57a3b..d78bdc4e 100644 --- a/jfuse-win/src/main/java/org/cryptomator/jfuse/win/extr/errno_h.java +++ b/jfuse-win/src/main/java/org/cryptomator/jfuse/win/extr/errno_h.java @@ -2,6 +2,10 @@ package org.cryptomator.jfuse.win.extr; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.VarHandle; +import java.nio.ByteOrder; +import java.lang.foreign.*; import static java.lang.foreign.ValueLayout.*; public class errno_h { @@ -41,6 +45,12 @@ public static int EISDIR() { public static int EROFS() { return (int)30L; } + public static int ENAMETOOLONG() { + return (int)38L; + } + public static int ENOLCK() { + return (int)39L; + } public static int ENOSYS() { return (int)40L; } @@ -50,6 +60,12 @@ public static int ENOTEMPTY() { public static int EINVAL() { return (int)22L; } + public static int ERANGE() { + return (int)34L; + } + public static int ENOTSUP() { + return (int)129L; + } } diff --git a/jfuse-win/src/test/java/org/cryptomator/jfuse/win/FuseImplTest.java b/jfuse-win/src/test/java/org/cryptomator/jfuse/win/FuseImplTest.java index a1cd334f..6a2523ae 100644 --- a/jfuse-win/src/test/java/org/cryptomator/jfuse/win/FuseImplTest.java +++ b/jfuse-win/src/test/java/org/cryptomator/jfuse/win/FuseImplTest.java @@ -227,19 +227,87 @@ public void testUtimens(long sec0, long nsec0, long sec1, long nsec1) { } } - @Test - @DisplayName("getattr") - public void testGetattr() { - try (var scope = MemorySession.openConfined()) { - var path = scope.allocateUtf8String("/foo"); - var attr = fuse_stat.allocate(scope); - var fi = scope.allocate(fuse3_file_info.$LAYOUT()); - Mockito.doReturn(42).when(fuseOps).getattr(Mockito.eq("/foo"), Mockito.any(), Mockito.argThat(usesSameMemorySegement(fi))); + @Nested + @DisplayName("attr") + public class Attr { + + @Test + @DisplayName("getattr") + public void testGetattr() { + try (var scope = MemorySession.openConfined()) { + var path = scope.allocateUtf8String("/foo"); + var attr = fuse_stat.allocate(scope); + var fi = scope.allocate(fuse3_file_info.$LAYOUT()); + Mockito.doReturn(42).when(fuseOps).getattr(Mockito.eq("/foo"), Mockito.any(), Mockito.argThat(usesSameMemorySegement(fi))); - var result = fuseImpl.getattr(path.address(), attr.address(), fi.address()); + var result = fuseImpl.getattr(path.address(), attr.address(), fi.address()); - Assertions.assertEquals(42, result); + Assertions.assertEquals(42, result); + } } + + @Test + @DisplayName("getxattr") + public void testGetxattr() { + try (var scope = MemorySession.openConfined()) { + var path = scope.allocateUtf8String("/foo"); + var name = scope.allocateUtf8String("bar"); + var value = scope.allocate(100); + + Mockito.doReturn(42).when(fuseOps).getxattr(Mockito.eq("/foo"), Mockito.eq("bar"), Mockito.any()); + + var result = fuseImpl.getxattr(path.address(), name.address(), value.address(), 100); + + Assertions.assertEquals(42, result); + } + } + + @Test + @DisplayName("setxattr") + public void testSetxattr() { + try (var scope = MemorySession.openConfined()) { + var path = scope.allocateUtf8String("/foo"); + var name = scope.allocateUtf8String("bar"); + var value = scope.allocate(100); + + Mockito.doReturn(42).when(fuseOps).setxattr(Mockito.eq("/foo"), Mockito.eq("bar"), Mockito.any(), Mockito.anyInt()); + + var result = fuseImpl.setxattr(path.address(), name.address(), value.address(), 100, 0xDEADBEEF); + + Assertions.assertEquals(42, result); + } + } + + @Test + @DisplayName("listxattr") + public void testListxattr() { + try (var scope = MemorySession.openConfined()) { + var path = scope.allocateUtf8String("/foo"); + var list = scope.allocate(100); + + Mockito.doReturn(42).when(fuseOps).listxattr(Mockito.eq("/foo"), Mockito.any()); + + var result = fuseImpl.listxattr(path.address(), list.address(), 100); + + Assertions.assertEquals(42, result); + } + } + + @Test + @DisplayName("removexattr") + public void testRemovexattr() { + try (var scope = MemorySession.openConfined()) { + var path = scope.allocateUtf8String("/foo"); + var name = scope.allocateUtf8String("bar"); + + Mockito.doReturn(42).when(fuseOps).removexattr(Mockito.eq("/foo"), Mockito.eq("bar")); + + var result = fuseImpl.removexattr(path.address(), name.address()); + + Assertions.assertEquals(42, result); + } + } + } diff --git a/jfuse/pom.xml b/jfuse/pom.xml index 20dfa0d7..3f8f49f2 100644 --- a/jfuse/pom.xml +++ b/jfuse/pom.xml @@ -5,7 +5,7 @@ org.cryptomator jfuse-parent - 0.3.3 + 0.4.0 4.0.0 jfuse diff --git a/pom.xml b/pom.xml index 2608adca..06cbdf49 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.cryptomator jfuse-parent pom - 0.3.3 + 0.4.0 jFUSE Java bindings for FUSE using foreign functions & memory API https://github.com/cryptomator/jfuse