diff --git a/jfuse-api/pom.xml b/jfuse-api/pom.xml
index 3dc7fbc0..6e4f7489 100644
--- a/jfuse-api/pom.xml
+++ b/jfuse-api/pom.xml
@@ -5,7 +5,7 @@
org.cryptomator
jfuse-parent
- 0.4.1
+ 0.4.2
4.0.0
jfuse-api
diff --git a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/Fuse.java b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/Fuse.java
index 975af65f..61f6ced4 100644
--- a/jfuse-api/src/main/java/org/cryptomator/jfuse/api/Fuse.java
+++ b/jfuse-api/src/main/java/org/cryptomator/jfuse/api/Fuse.java
@@ -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;
@@ -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");
@@ -119,19 +125,24 @@ 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 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 fuseLoop) throws InterruptedException {
var probe = Files.getFileAttributeView(mountPoint.resolve(MOUNT_PROBE.substring(1)), BasicFileAttributeView.class);
do {
try {
@@ -139,7 +150,7 @@ void waitForMountingToComplete(Path mountPoint) throws InterruptedException {
} catch (IOException e) {
// noop
}
- } while (!mountProbeSucceeded.await(200, TimeUnit.MILLISECONDS));
+ } while (!fuseLoop.isDone() && !mountProbeSucceeded.await(200, TimeUnit.MILLISECONDS));
}
@Blocking
diff --git a/jfuse-api/src/test/java/org/cryptomator/jfuse/api/FuseTest.java b/jfuse-api/src/test/java/org/cryptomator/jfuse/api/FuseTest.java
index 2fb864d8..fa1ea532 100644
--- a/jfuse-api/src/test/java/org/cryptomator/jfuse/api/FuseTest.java
+++ b/jfuse-api/src/test/java/org/cryptomator/jfuse/api/FuseTest.java
@@ -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);
@@ -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 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 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
@@ -58,23 +108,23 @@ protected void bind(FuseOperations.Operation operation) {
@Override
protected FuseMount mount(List 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() {
}
}
diff --git a/jfuse-examples/pom.xml b/jfuse-examples/pom.xml
index be015173..15c100df 100644
--- a/jfuse-examples/pom.xml
+++ b/jfuse-examples/pom.xml
@@ -5,7 +5,7 @@
org.cryptomator
jfuse-parent
- 0.4.1
+ 0.4.2
4.0.0
jfuse-examples
diff --git a/jfuse-linux-aarch64/pom.xml b/jfuse-linux-aarch64/pom.xml
index 0aa75f6e..e35adde6 100644
--- a/jfuse-linux-aarch64/pom.xml
+++ b/jfuse-linux-aarch64/pom.xml
@@ -5,7 +5,7 @@
jfuse-parent
org.cryptomator
- 0.4.1
+ 0.4.2
4.0.0
jfuse-linux-aarch64
diff --git a/jfuse-linux-amd64/pom.xml b/jfuse-linux-amd64/pom.xml
index b124c530..9259d906 100644
--- a/jfuse-linux-amd64/pom.xml
+++ b/jfuse-linux-amd64/pom.xml
@@ -5,7 +5,7 @@
org.cryptomator
jfuse-parent
- 0.4.1
+ 0.4.2
4.0.0
jfuse-linux-amd64
diff --git a/jfuse-mac/pom.xml b/jfuse-mac/pom.xml
index 77453e67..56c2c0a0 100644
--- a/jfuse-mac/pom.xml
+++ b/jfuse-mac/pom.xml
@@ -5,7 +5,7 @@
org.cryptomator
jfuse-parent
- 0.4.1
+ 0.4.2
4.0.0
jfuse-mac
diff --git a/jfuse-tests/pom.xml b/jfuse-tests/pom.xml
index 683a628a..6465b8bd 100644
--- a/jfuse-tests/pom.xml
+++ b/jfuse-tests/pom.xml
@@ -5,7 +5,7 @@
org.cryptomator
jfuse-parent
- 0.4.1
+ 0.4.2
4.0.0
jfuse-tests
diff --git a/jfuse-win/pom.xml b/jfuse-win/pom.xml
index d8707f30..2fc80a1a 100644
--- a/jfuse-win/pom.xml
+++ b/jfuse-win/pom.xml
@@ -5,7 +5,7 @@
org.cryptomator
jfuse-parent
- 0.4.1
+ 0.4.2
4.0.0
jfuse-win
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 166860c9..b0504cea 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
@@ -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
diff --git a/jfuse/pom.xml b/jfuse/pom.xml
index f72c304a..b826d6ef 100644
--- a/jfuse/pom.xml
+++ b/jfuse/pom.xml
@@ -5,7 +5,7 @@
org.cryptomator
jfuse-parent
- 0.4.1
+ 0.4.2
4.0.0
jfuse
diff --git a/pom.xml b/pom.xml
index 3e8a0160..51300262 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
org.cryptomator
jfuse-parent
pom
- 0.4.1
+ 0.4.2
jFUSE
Java bindings for FUSE using foreign functions & memory API
https://github.com/cryptomator/jfuse