Skip to content

Commit

Permalink
Merge branch 'release/0.4.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
infeo committed Mar 22, 2023
2 parents 75c2070 + 601b4df commit eb45bed
Show file tree
Hide file tree
Showing 12 changed files with 99 additions and 38 deletions.
2 changes: 1 addition & 1 deletion jfuse-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>jfuse-parent</artifactId>
<version>0.4.1</version>
<version>0.4.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jfuse-api</artifactId>
Expand Down
21 changes: 16 additions & 5 deletions jfuse-api/src/main/java/org/cryptomator/jfuse/api/Fuse.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
Expand Down Expand Up @@ -105,7 +107,11 @@ public static FuseBuilder builder() {
*/
@Blocking
@MustBeInvokedByOverriders
public void mount(String progName, Path mountPoint, String... flags) throws FuseMountFailedException, IllegalArgumentException {
public synchronized void mount(String progName, Path mountPoint, String... flags) throws FuseMountFailedException, IllegalArgumentException {
if (!fuseScope.isAlive()) {
throw new IllegalStateException("Already closed"); //TODO: throw specialized exception
}

FuseMount lock = new UnmountedFuseMount();
if (!mount.compareAndSet(UNMOUNTED, lock)) {
throw new IllegalStateException("Already mounted");
Expand All @@ -119,27 +125,32 @@ public void mount(String progName, Path mountPoint, String... flags) throws Fuse

try {
var fuseMount = this.mount(args);
executor.submit(() -> fuseLoop(fuseMount)); // TODO keep reference of future and report result
waitForMountingToComplete(mountPoint);
Future<Integer> fuseLoop = executor.submit(() -> fuseLoop(fuseMount));
waitForMountingToComplete(mountPoint, fuseLoop);
if (fuseLoop.isDone()) {
throw new FuseMountFailedException("fuse_loop() returned prematurely with non-zero exit code " + fuseLoop.get());
}
mount.compareAndSet(lock, fuseMount);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new FuseMountFailedException("Interrupted while waiting for mounting to finish");
} catch (ExecutionException e) {
throw new FuseMountFailedException("Exception when starting fuse_loop. Message: " + e.getCause().getMessage());
} finally {
mount.compareAndSet(lock, UNMOUNTED); // if value is still `lock`, mount has failed.
}
}

@VisibleForTesting
void waitForMountingToComplete(Path mountPoint) throws InterruptedException {
void waitForMountingToComplete(Path mountPoint, Future<Integer> fuseLoop) throws InterruptedException {
var probe = Files.getFileAttributeView(mountPoint.resolve(MOUNT_PROBE.substring(1)), BasicFileAttributeView.class);
do {
try {
probe.readAttributes(); // we don't care about the result, we just want to trigger a getattr call
} catch (IOException e) {
// noop
}
} while (!mountProbeSucceeded.await(200, TimeUnit.MILLISECONDS));
} while (!fuseLoop.isDone() && !mountProbeSucceeded.await(200, TimeUnit.MILLISECONDS));
}

@Blocking
Expand Down
96 changes: 73 additions & 23 deletions jfuse-api/src/test/java/org/cryptomator/jfuse/api/FuseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@
import java.nio.file.spi.FileSystemProvider;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.Future;

public class FuseTest {

private FuseOperations fuseOps = Mockito.mock(FuseOperations.class);
private Fuse fuse = Mockito.spy(new FuseStub(fuseOps));
private final FuseOperations fuseOps = Mockito.mock(FuseOperations.class);
private final FuseMount fuseMount = Mockito.spy(new FuseMountStub());
private final Fuse fuse = Mockito.spy(new FuseStub(fuseMount, fuseOps));
private final Path mountPoint = Mockito.mock(Path.class, "/mnt");

@Test
@DisplayName("waitForMountingToComplete() waits for getattr(\"/jfuse_mount_probe\")")
public void testWaitForMountingToComplete() throws IOException {
Path mountPoint = Mockito.mock(Path.class, "/mnt");
Path probePath = Mockito.mock(Path.class, "/mnt/jfuse_mount_probe");
FileSystem fs = Mockito.mock(FileSystem.class);
FileSystemProvider fsProv = Mockito.mock(FileSystemProvider.class);
Expand All @@ -39,16 +41,64 @@ public void testWaitForMountingToComplete() throws IOException {
fuse.fuseOperations.getattr("/jfuse_mount_probe", Mockito.mock(Stat.class), Mockito.mock(FileInfo.class));
throw new NoSuchFileException("/mnt/jfuse_mount_probe still not found");
}).when(attrView).readAttributes();
Future<Integer> fuseLoop = Mockito.mock(Future.class);
Mockito.doReturn(false).when(fuseLoop).isDone();

Assertions.assertTimeoutPreemptively(Duration.ofSeconds(1), () -> fuse.waitForMountingToComplete(mountPoint));
Assertions.assertTimeoutPreemptively(Duration.ofSeconds(1), () -> fuse.waitForMountingToComplete(mountPoint, fuseLoop));
Mockito.verify(fuseLoop, Mockito.atLeastOnce()).isDone();
}

Mockito.verify(fuseOps).getattr(Mockito.eq("/jfuse_mount_probe"), Mockito.any(), Mockito.any());
@Test
@DisplayName("waitForMountingToComplete() waits returns immediately if fuse_loop fails")
public void testPrematurelyFuseLoopReturn() throws IOException {
Path probePath = Mockito.mock(Path.class, "/mnt/jfuse_mount_probe");
FileSystem fs = Mockito.mock(FileSystem.class);
FileSystemProvider fsProv = Mockito.mock(FileSystemProvider.class);
BasicFileAttributeView attrView = Mockito.mock(BasicFileAttributeView.class);
Mockito.doReturn(probePath).when(mountPoint).resolve("jfuse_mount_probe");
Mockito.doReturn(fs).when(probePath).getFileSystem();
Mockito.doReturn(fsProv).when(fs).provider();
Mockito.doReturn(attrView).when(fsProv).getFileAttributeView(probePath, BasicFileAttributeView.class);
Future<Integer> fuseLoop = Mockito.mock(Future.class);
Mockito.doReturn(true).when(fuseLoop).isDone();

Assertions.assertTimeoutPreemptively(Duration.ofSeconds(1), () -> fuse.waitForMountingToComplete(mountPoint, fuseLoop));
Mockito.verify(fuseLoop, Mockito.atLeastOnce()).isDone();
}

@Test
@DisplayName("Already closed fuseMount throws IllegalStateException on mount")
public void testMountThrowsIllegalStateIfClosed() {
Assertions.assertDoesNotThrow(fuse::close);
Assertions.assertThrows(IllegalStateException.class, () -> fuse.mount("test3000", mountPoint));
}

@Test
@DisplayName("Already mounted fuseMount throws IllegalStateException on mount")
public void testMountThrowsIllegalStateIfAlreadyMounted() throws InterruptedException {
Mockito.doNothing().when(fuse).waitForMountingToComplete(Mockito.eq(mountPoint), Mockito.any());
Assertions.assertDoesNotThrow(() -> fuse.mount("test3000", mountPoint));
Assertions.assertThrows(IllegalStateException.class, () -> fuse.mount("test3000", mountPoint));
}

@Test
@DisplayName("If fuse_loop instantly returns with non-zero result, throw FuseMountFailedException")
public void testMountThrowsFuseMountFailedIfLoopReturnsNonZero() throws InterruptedException {
Mockito.doAnswer(invocation -> {
Thread.sleep(1000);
return null;
}).when(fuse).waitForMountingToComplete(Mockito.eq(mountPoint), Mockito.any());
Mockito.doReturn(1).when(fuseMount).loop();
Assertions.assertThrows(FuseMountFailedException.class, () -> fuse.mount("test3000", mountPoint));
}

private static class FuseStub extends Fuse {

protected FuseStub(FuseOperations fuseOperations) {
FuseMount fuseMount;

protected FuseStub(FuseMount mountStub, FuseOperations fuseOperations) {
super(fuseOperations, allocator -> allocator.allocate(0L));
this.fuseMount = mountStub;
}

@Override
Expand All @@ -58,23 +108,23 @@ protected void bind(FuseOperations.Operation operation) {

@Override
protected FuseMount mount(List<String> args) {
return new FuseMount() {

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

@Override
public void unmount() {
// no-op
}

@Override
public void destroy() {
// no-op
}
};
return fuseMount;
}
}

private record FuseMountStub() implements FuseMount {

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

@Override
public void unmount() {
}

@Override
public void destroy() {
}
}

Expand Down
2 changes: 1 addition & 1 deletion jfuse-examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>jfuse-parent</artifactId>
<version>0.4.1</version>
<version>0.4.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jfuse-examples</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion jfuse-linux-aarch64/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>jfuse-parent</artifactId>
<groupId>org.cryptomator</groupId>
<version>0.4.1</version>
<version>0.4.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jfuse-linux-aarch64</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion jfuse-linux-amd64/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>jfuse-parent</artifactId>
<version>0.4.1</version>
<version>0.4.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jfuse-linux-amd64</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion jfuse-mac/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>jfuse-parent</artifactId>
<version>0.4.1</version>
<version>0.4.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jfuse-mac</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion jfuse-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>jfuse-parent</artifactId>
<version>0.4.1</version>
<version>0.4.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jfuse-tests</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion jfuse-win/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>jfuse-parent</artifactId>
<version>0.4.1</version>
<version>0.4.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jfuse-win</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public FuseImpl(FuseOperations fuseOperations) {
}

@Override
public void mount(String progName, Path mountPoint, String... flags) throws FuseMountFailedException {
public synchronized void mount(String progName, Path mountPoint, String... flags) throws FuseMountFailedException {
var adjustedMP = mountPoint;
if (mountPoint.equals(mountPoint.getRoot()) && mountPoint.isAbsolute()) {
//winfsp accepts only drive letters written in drive relative notation
Expand Down
2 changes: 1 addition & 1 deletion jfuse/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>jfuse-parent</artifactId>
<version>0.4.1</version>
<version>0.4.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jfuse</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<groupId>org.cryptomator</groupId>
<artifactId>jfuse-parent</artifactId>
<packaging>pom</packaging>
<version>0.4.1</version>
<version>0.4.2</version>
<name>jFUSE</name>
<description>Java bindings for FUSE using foreign functions &amp; memory API</description>
<url>https://github.com/cryptomator/jfuse</url>
Expand Down

0 comments on commit eb45bed

Please sign in to comment.