Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for uid/gid and chown #19

Merged
merged 5 commits into from
Oct 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Not all [`fuse_operations`](https://libfuse.github.io/doxygen/structfuse__operat
| rename | :white_check_mark: |
| link | :x: |
| chmod | :white_check_mark: |
| chown | :x: |
| chown | :white_check_mark: |
| truncate | :white_check_mark: |
| ~ftruncate~ | use truncate |
| ~utime~ | use utimens |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public interface FuseOperations {
enum Operation {
ACCESS,
CHMOD,
CHOWN,
CREATE,
DESTROY,
GET_ATTR,
Expand Down
8 changes: 8 additions & 0 deletions jfuse-api/src/main/java/org/cryptomator/jfuse/api/Stat.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ public interface Stat {

int getMode();

void setUid(int uid);

int getUid();

void setGid(int gid);

int getGid();

infeo marked this conversation as resolved.
Show resolved Hide resolved
void setNLink(short count);

long getNLink();
Expand Down
70 changes: 11 additions & 59 deletions jfuse-api/src/test/java/org/cryptomator/jfuse/api/StatTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class StatTest {

private static final int MAGIC = 123456;//some rando number

public Stat stat = Mockito.spy(new StubStatImpl());
public Stat stat = Mockito.mock(Stat.class);

@Nested
@DisplayName("Test File Type Predicates")
Expand Down Expand Up @@ -99,7 +99,8 @@ public class Permissions {
"0010644,rw-r--r--"
})
public void getPermissions(String mode, String perms) {
Mockito.when(stat.getMode()).thenReturn(Integer.valueOf(mode, 8));
Mockito.doReturn(Integer.valueOf(mode, 8)).when(stat).getMode();
Mockito.doCallRealMethod().when(stat).getPermissions();
var expected = PosixFilePermissions.fromString(perms);

var result = stat.getPermissions();
Expand All @@ -114,7 +115,8 @@ public void getPermissions(String mode, String perms) {
"0644,rw-r--r--",
})
public void setPermissions(String mode, String perms) {
Mockito.when(stat.getMode()).thenReturn(S_IFDIR);
Mockito.doReturn(S_IFDIR).when(stat).getMode();
Mockito.doCallRealMethod().when(stat).setPermissions(Mockito.any());
var permissions = PosixFilePermissions.fromString(perms);

stat.setPermissions(permissions);
Expand All @@ -127,7 +129,8 @@ public void setPermissions(String mode, String perms) {
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testHasMode(boolean isPresent) {
Mockito.when(stat.getMode()).thenReturn(isPresent ? MAGIC : ~MAGIC);
Mockito.doReturn(isPresent ? MAGIC : ~MAGIC).when(stat).getMode();
Mockito.doCallRealMethod().when(stat).hasMode(Mockito.anyInt());

boolean result = stat.hasMode(MAGIC);

Expand All @@ -137,7 +140,8 @@ public void testHasMode(boolean isPresent) {
@Test
@DisplayName("setModeBits()")
public void testSetModeBits() {
Mockito.when(stat.getMode()).thenReturn(0755);
Mockito.doReturn(0755).when(stat).getMode();
Mockito.doCallRealMethod().when(stat).setModeBits(Mockito.anyInt());

stat.setModeBits(S_IFDIR);

Expand All @@ -147,64 +151,12 @@ public void testSetModeBits() {
@Test
@DisplayName("unsetModeBits()")
public void testUnsetModeBits() {
Mockito.when(stat.getMode()).thenReturn(S_IFDIR | 0755);
Mockito.doReturn(S_IFDIR | 0755).when(stat).getMode();
Mockito.doCallRealMethod().when(stat).unsetModeBits(Mockito.anyInt());

stat.unsetModeBits(S_IFDIR);

Mockito.verify(stat).setMode(0755);
}

private static class StubStatImpl implements Stat {

@Override
public TimeSpec aTime() {
return null;
}

@Override
public TimeSpec cTime() {
return null;
}

@Override
public TimeSpec mTime() {
return null;
}

@Override
public TimeSpec birthTime() {
return null;
}

@Override
public void setMode(int mode) {

}

@Override
public int getMode() {
return 0;
}

@Override
public void setNLink(short count) {

}

@Override
public long getNLink() {
return 0;
}

@Override
public void setSize(long size) {

}

@Override
public long getSize() {
return 0;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ private void bind(FuseOperations.Operation operation) {
case INIT -> fuse_operations.init$set(fuseOps, fuse_operations.init.allocate(this::init, fuseScope).address());
case ACCESS -> fuse_operations.access$set(fuseOps, fuse_operations.access.allocate(this::access, fuseScope).address());
case CHMOD -> fuse_operations.chmod$set(fuseOps, fuse_operations.chmod.allocate(this::chmod, fuseScope).address());
case CHOWN -> fuse_operations.chown$set(fuseOps, fuse_operations.chown.allocate(this::chown, fuseScope).address());
case CREATE -> fuse_operations.create$set(fuseOps, fuse_operations.create.allocate(this::create, fuseScope).address());
case DESTROY -> fuse_operations.destroy$set(fuseOps, fuse_operations.destroy.allocate(this::destroy, fuseScope).address());
case GET_ATTR -> fuse_operations.getattr$set(fuseOps, fuse_operations.getattr.allocate(this::getattr, fuseScope).address());
Expand Down Expand Up @@ -119,6 +120,13 @@ private int chmod(MemoryAddress path, int mode, MemoryAddress fi) {
}
}

@VisibleForTesting
int chown(MemoryAddress path, int uid, int gid, MemoryAddress fi) {
try (var scope = MemorySession.openConfined()) {
return delegate.chown(path.getUtf8String(0), uid, gid, new FileInfoImpl(fi, scope));
}
}

private int create(MemoryAddress path, int mode, MemoryAddress fi) {
try (var scope = MemorySession.openConfined()) {
return delegate.create(path.getUtf8String(0), mode, new FileInfoImpl(fi, scope));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,26 @@ public int getMode() {
return stat.st_mode$get(segment);
}

@Override
public void setUid(int uid) {
stat.st_uid$set(segment, uid);
}

@Override
public int getUid() {
return stat.st_uid$get(segment);
}

@Override
public void setGid(int gid) {
stat.st_gid$set(segment, gid);
}

@Override
public int getGid() {
return stat.st_gid$get(segment);
}

@Override
public void setNLink(short count) {
stat.st_nlink$set(segment, count);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,17 @@ public void testUtimens(long sec0, long nsec0, long sec1, long nsec1) {
}
}

@Test
@DisplayName("chown")
public void testChown() {
try (var scope = MemorySession.openConfined()) {
var path = scope.allocateUtf8String("/foo");
var fi = fuse_file_info.allocate(scope);
Mockito.doReturn(42).when(fuseOps).chown(Mockito.eq("/foo"), Mockito.eq(42), Mockito.eq(1337), Mockito.any());

var result = fuseImpl.chown(path.address(), 42, 1337, fi.address());

Assertions.assertEquals(42, result);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package org.cryptomator.jfuse.linux.aarch64;

import org.cryptomator.jfuse.api.Stat;
import org.cryptomator.jfuse.linux.aarch64.extr.stat;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.lang.foreign.MemorySegment;
import java.lang.foreign.MemorySession;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Stream;

public class StatImplTest {

@DisplayName("test getters")
@ParameterizedTest(name = "{1}")
@MethodSource
public void testGetters(SetInMemorySegment<Number> setter, GetInStat<Number> getter, Number value) {
try (var scope = MemorySession.openConfined()) {
var segment = stat.allocate(scope);
var stat = new StatImpl(segment.address(), scope);

setter.accept(segment, value);

Assertions.assertEquals(value.longValue(), getter.apply(stat).longValue());
}
}

public static Stream<Arguments> testGetters() {
return Stream.of(
Arguments.arguments((SetInMemorySegment<Integer>) stat::st_mode$set, Named.of("getMode()", (GetInStat<Integer>) Stat::getMode), 42),
Arguments.arguments((SetInMemorySegment<Integer>) stat::st_uid$set, Named.of("getUid()", (GetInStat<Integer>) Stat::getUid), 42),
Arguments.arguments((SetInMemorySegment<Integer>) stat::st_gid$set, Named.of("getGid()", (GetInStat<Integer>) Stat::getGid), 42),
Arguments.arguments((SetInMemorySegment<Integer>) stat::st_nlink$set, Named.of("getNLink()", (GetInStat<Long>) Stat::getNLink), 42),
Arguments.arguments((SetInMemorySegment<Long>) stat::st_size$set, Named.of("getSize()", (GetInStat<Long>) Stat::getSize), 42L)
);
}

private interface SetInMemorySegment<T> extends BiConsumer<MemorySegment, T> {}

private interface GetInStat<T> extends Function<Stat, T> {}

@DisplayName("test setters")
@ParameterizedTest(name = "{0}")
@MethodSource
public void testSetters(SetInStat<Number> setter, GetInMemorySegment<Number> getter, Number value) {
try (var scope = MemorySession.openConfined()) {
var segment = stat.allocate(scope);
var stat = new StatImpl(segment.address(), scope);

setter.accept(stat, value);

Assertions.assertEquals(value.longValue(), getter.apply(segment).longValue());
}
}

public static Stream<Arguments> testSetters() {
return Stream.of(
Arguments.arguments(Named.of("setMode()", (SetInStat<Integer>) Stat::setMode), (GetInMemorySegment<Integer>) stat::st_mode$get, 42),
Arguments.arguments(Named.of("setUid()", (SetInStat<Integer>) Stat::setUid), (GetInMemorySegment<Integer>) stat::st_uid$get, 42),
Arguments.arguments(Named.of("setGid()", (SetInStat<Integer>) Stat::setGid), (GetInMemorySegment<Integer>) stat::st_gid$get, 42),
Arguments.arguments(Named.of("setNLink()", (SetInStat<Short>) Stat::setNLink), (GetInMemorySegment<Integer>) stat::st_nlink$get, (short) 42),
Arguments.arguments(Named.of("setSize()", (SetInStat<Long>) Stat::setSize), (GetInMemorySegment<Long>) stat::st_size$get, 42L)
);
}

private interface SetInStat<T> extends BiConsumer<Stat, T> {}

private interface GetInMemorySegment<T> extends Function<MemorySegment, T> {}

}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ private void bind(FuseOperations.Operation operation) {
case INIT -> fuse_operations.init$set(fuseOps, fuse_operations.init.allocate(this::init, fuseScope).address());
case ACCESS -> fuse_operations.access$set(fuseOps, fuse_operations.access.allocate(this::access, fuseScope).address());
case CHMOD -> fuse_operations.chmod$set(fuseOps, fuse_operations.chmod.allocate(this::chmod, fuseScope).address());
case CHOWN -> fuse_operations.chown$set(fuseOps, fuse_operations.chown.allocate(this::chown, fuseScope).address());
case CREATE -> fuse_operations.create$set(fuseOps, fuse_operations.create.allocate(this::create, fuseScope).address());
case DESTROY -> fuse_operations.destroy$set(fuseOps, fuse_operations.destroy.allocate(this::destroy, fuseScope).address());
case GET_ATTR -> fuse_operations.getattr$set(fuseOps, fuse_operations.getattr.allocate(this::getattr, fuseScope).address());
Expand Down Expand Up @@ -119,6 +120,13 @@ private int chmod(MemoryAddress path, int mode, MemoryAddress fi) {
}
}

@VisibleForTesting
int chown(MemoryAddress path, int uid, int gid, MemoryAddress fi) {
try (var scope = MemorySession.openConfined()) {
return delegate.chown(path.getUtf8String(0), uid, gid, new FileInfoImpl(fi, scope));
}
}

private int create(MemoryAddress path, int mode, MemoryAddress fi) {
try (var scope = MemorySession.openConfined()) {
return delegate.create(path.getUtf8String(0), mode, new FileInfoImpl(fi, scope));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,26 @@ public int getMode() {
return stat.st_mode$get(segment);
}

@Override
public void setUid(int uid) {
stat.st_uid$set(segment, uid);
}

@Override
public int getUid() {
return stat.st_uid$get(segment);
}

@Override
public void setGid(int gid) {
stat.st_gid$set(segment, gid);
}

@Override
public int getGid() {
return stat.st_gid$get(segment);
}

@Override
public void setNLink(short count) {
stat.st_nlink$set(segment, count);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,18 @@ public void testUtimens(long sec0, long nsec0, long sec1, long nsec1) {
}
}

@Test
@DisplayName("chown")
public void testChown() {
try (var scope = MemorySession.openConfined()) {
var path = scope.allocateUtf8String("/foo");
var fi = fuse_file_info.allocate(scope);
Mockito.doReturn(42).when(fuseOps).chown(Mockito.eq("/foo"), Mockito.eq(42), Mockito.eq(1337), Mockito.any());

var result = fuseImpl.chown(path.address(), 42, 1337, fi.address());

Assertions.assertEquals(42, result);
}
}

}