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