From 2ce7e2f5cbbe1be2000c3d0ab21a32de0aefc0c7 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 23 Feb 2021 12:19:05 +0100 Subject: [PATCH 01/17] bump version of dependency check tool [ci skip] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1cf4fdb..f5bec6c 100644 --- a/pom.xml +++ b/pom.xml @@ -194,7 +194,7 @@ org.owasp dependency-check-maven - 6.1.0 + 6.1.1 24 0 From 4c55c1a66b1af5477636d61d4e29870f72c2dd80 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Mon, 22 Mar 2021 12:33:43 +0100 Subject: [PATCH 02/17] Update README.md [ci skip] --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 23a6d47..1144c2e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Build Status](https://github.com/cryptomator/dokany-nio-adapter/workflows/Build/badge.svg)](https://github.com/cryptomator/dokany-nio-adapter/actions?query=workflow%3ABuild) -[![Codacy Code Quality](https://app.codacy.com/project/badge/Grade/f3f46474adb14bc59d4fc489903950f5)](https://www.codacy.com/gh/cryptomator/dokany-nio-adapter) -[![Codacy Coverage](https://app.codacy.com/project/badge/Coverage/f3f46474adb14bc59d4fc489903950f5)](https://www.codacy.com/gh/cryptomator/dokany-nio-adapter) +[![Codacy Code Quality](https://app.codacy.com/project/badge/Grade/f3f46474adb14bc59d4fc489903950f5)](https://www.codacy.com/gh/cryptomator/dokany-nio-adapter/dashboard) +[![Codacy Coverage](https://app.codacy.com/project/badge/Coverage/f3f46474adb14bc59d4fc489903950f5)](https://www.codacy.com/gh/cryptomator/dokany-nio-adapter/dashboard) [![Known Vulnerabilities](https://snyk.io/test/github/cryptomator/dokany-nio-adapter/badge.svg)](https://snyk.io/test/github/cryptomator/dokany-nio-adapter) # dokany-nio-adapter From 37aa3f05096582f10a83dc0c8375e68600d961ca Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Mon, 22 Mar 2021 12:34:15 +0100 Subject: [PATCH 03/17] updated slack notification [ci skip] --- .github/workflows/publish-github.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-github.yml b/.github/workflows/publish-github.yml index 32a3041..8be609b 100644 --- a/.github/workflows/publish-github.yml +++ b/.github/workflows/publish-github.yml @@ -35,6 +35,6 @@ jobs: SLACK_ICON_EMOJI: ':bot:' SLACK_CHANNEL: 'cryptomator-desktop' SLACK_TITLE: "Published ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}" - SLACK_MESSAGE: "Ready to ." + SLACK_MESSAGE: "Ready to ." SLACK_FOOTER: - MSG_MINIMAL: true \ No newline at end of file + MSG_MINIMAL: true From c19a5d41b8835d6bf8bbd5a484a96f8566ea5cbb Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 23 Mar 2021 16:58:26 +0100 Subject: [PATCH 04/17] Remove redundant exception --- src/main/java/com/dokany/java/DokanyMount.java | 2 +- .../java/com/dokany/java/LibraryNotFoundException.java | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 src/main/java/com/dokany/java/LibraryNotFoundException.java diff --git a/src/main/java/com/dokany/java/DokanyMount.java b/src/main/java/com/dokany/java/DokanyMount.java index 83abf45..b422aea 100644 --- a/src/main/java/com/dokany/java/DokanyMount.java +++ b/src/main/java/com/dokany/java/DokanyMount.java @@ -117,7 +117,7 @@ public synchronized void mount(Executor executor) throws DokanyException { setIsMounted(true); } catch (UnsatisfiedLinkError err) { LOG.error("Unable to load dokan driver.", err); - throw new LibraryNotFoundException(err.getMessage()); + throw new DokanyException(err); } } else { LOG.debug("Dokan Device already mounted on {}.", deviceOptions.MountPoint); diff --git a/src/main/java/com/dokany/java/LibraryNotFoundException.java b/src/main/java/com/dokany/java/LibraryNotFoundException.java deleted file mode 100644 index 636da1d..0000000 --- a/src/main/java/com/dokany/java/LibraryNotFoundException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.dokany.java; - -public class LibraryNotFoundException extends RuntimeException{ - - public LibraryNotFoundException(String message) { - super(message); - } - -} From 7be96b3945b4cc23e6921716f81eb70aa8f45989 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 25 Mar 2021 10:56:11 +0100 Subject: [PATCH 05/17] Closes #49 Overload mount method to offer the possibility to add an afterUnmount action (e.g. mount observer) --- .../java/com/dokany/java/DokanyMount.java | 86 +++++++++++-------- .../com/dokany/java/constants/MountError.java | 2 +- .../frontend/dokany/MountFactory.java | 39 +++++++-- .../frontend/dokany/ReadWriteMirrorTest.java | 5 +- 4 files changed, 88 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/dokany/java/DokanyMount.java b/src/main/java/com/dokany/java/DokanyMount.java index b422aea..7c429b8 100644 --- a/src/main/java/com/dokany/java/DokanyMount.java +++ b/src/main/java/com/dokany/java/DokanyMount.java @@ -15,6 +15,7 @@ import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.BiFunction; /** * Main class to start and stop Dokany file system. @@ -28,7 +29,7 @@ public final class DokanyMount implements Mount { private final SafeUnmountCheck unmountCheck; private volatile boolean isMounted; - private volatile CompletableFuture mountFuture; + private volatile CompletableFuture mountFuture; public DokanyMount(final DeviceOptions deviceOptions, final DokanyFileSystem fileSystem) { this(deviceOptions, fileSystem, () -> true); @@ -64,20 +65,41 @@ public long getVersion() { /** * Calls {@link NativeMethods#DokanMain(DeviceOptions, DokanyOperations)} in a thread of the Common ForkJoin Pool. - * Adds to the JVM a {@link java.lang.Runtime#addShutdownHook(Thread)} which calls {@link #close()} + * No-op if this object is already mounted. + *

+ * Additionally a shutdown hook invoking {@link #close()} is registered to the JVM. */ public synchronized void mount() throws DokanyException { - this.mount(ForkJoinPool.commonPool()); + this.mount(ForkJoinPool.commonPool(), () -> {}); } + /** * Calls {@link NativeMethods#DokanMain(DeviceOptions, DokanyOperations)} on the given executor. - * Adds to the JVM a {@link java.lang.Runtime#addShutdownHook(Thread)} which calls {@link #close()} + * No-op if this object is already mounted. + *

+ * Additionally a shutdown hook invoking {@link #close()} is registered to the JVM. * - * @Param Executor executor + * @param executor + * @throws DokanyException */ public synchronized void mount(Executor executor) throws DokanyException { + this.mount(executor, () -> {}); + } + + + /** + * Calls {@link NativeMethods#DokanMain(DeviceOptions, DokanyOperations)} on the given executor and runs after mount termination a specified action. + * No-op if this object is already mounted. + *

+ * Additionally a shutdown hook invoking {@link #close()} is registered to the JVM. + * + * @param executor + * @param afterUnmountAction object with a run() method which is executed after unmount + */ + public synchronized void mount(Executor executor, Runnable afterUnmountAction) throws DokanyException { if (!isMounted) { + isMounted = true; try { Runtime.getRuntime().addShutdownHook(new Thread(this::close)); @@ -85,49 +107,43 @@ public synchronized void mount(Executor executor) throws DokanyException { //real mount op mountFuture = CompletableFuture .supplyAsync(() -> NativeMethods.DokanMain(deviceOptions, new DokanyOperationsProxy(fileSystem)), executor) - .handle((returnVal, exception) -> { - setIsMounted(false); - if (returnVal != null && returnVal.equals(0)) { - return 0; - } - - if (returnVal == null) { + .handle((BiFunction) (returnVal, exception) -> { + isMounted = false; + afterUnmountAction.run(); + if (exception != null) { throw new DokanyException(exception); - } else { + } + if (returnVal != null && returnVal != 0) { throw new DokanyException("Return Code " + returnVal + ": " + MountError.fromInt(returnVal).getDescription()); } + return null; }); - //check return value - try { - mountFuture.get(1000, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - // up and running - } catch (ExecutionException e) { - LOG.error("Error while mounting", e); - if (e.getCause() instanceof DokanyException) { - throw (DokanyException) e.getCause(); - } else { - throw new DokanyException(e); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } + //check if mount runs + mountFuture.get(1000, TimeUnit.MILLISECONDS); - setIsMounted(true); + //if the execution reaches this point, was directly unmounted. + throw new DokanyException("Mount failed: Volume was instantly unmounted."); } catch (UnsatisfiedLinkError err) { LOG.error("Unable to load dokan driver.", err); throw new DokanyException(err); + } catch (ExecutionException e) { + LOG.error("Error while mounting", e.getCause()); + if (e.getCause() instanceof DokanyException) { + throw (DokanyException) e.getCause(); + } else { + throw new DokanyException(e.getCause()); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (TimeoutException e) { + //up and running } } else { LOG.debug("Dokan Device already mounted on {}.", deviceOptions.MountPoint); } } - public synchronized void setIsMounted(boolean newValue) { - isMounted = newValue; - } - /** * Unmounts the Dokan Device from the mount point given in the mount options. @@ -137,9 +153,9 @@ public synchronized void close() { LOG.info("Unmounting Dokan device at {}", deviceOptions.MountPoint); boolean unmounted = NativeMethods.DokanRemoveMountPoint(deviceOptions.MountPoint); if (unmounted) { - setIsMounted(false); + isMounted = false; } else { - LOG.error("Unable to unmount Dokan device at {}. Use dokanctl.exe to unmount", deviceOptions.MountPoint); + LOG.error("Unable to unmount dokan device at {}.", deviceOptions.MountPoint); } } } diff --git a/src/main/java/com/dokany/java/constants/MountError.java b/src/main/java/com/dokany/java/constants/MountError.java index ad1da28..4a14ddc 100644 --- a/src/main/java/com/dokany/java/constants/MountError.java +++ b/src/main/java/com/dokany/java/constants/MountError.java @@ -9,7 +9,7 @@ public enum MountError implements EnumInteger { DRIVE_LETTER_ERROR(-2, "Dokan mount failed - Bad drive letter."), DRIVER_INSTALL_ERROR(-3, "Dokan mount failed - Cannot install driver."), START_ERROR(-4, "Dokan mount failed - Driver answer that something is wrong."), - MOUNT_ERROR(-5, "Dokan mount failed -Cannot assign a drive letter or mount point. Probably already used by another volume."), + MOUNT_ERROR(-5, "Dokan mount failed - Cannot assign a drive letter or mount point. Probably already used by another volume."), MOUNT_POINT_ERROR(-6, "Dokan mount failed - Mount point is invalid."), VERSION_ERROR(-7, "Dokan mount failed - Requested an incompatible version."); diff --git a/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java b/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java index 6f3d15f..0dfb145 100644 --- a/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java +++ b/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java @@ -44,7 +44,7 @@ public MountFactory(ExecutorService executorService) { } /** - * Mounts a virtual drive at the given mount point containing contents of the given path. + * Mounts the root of a filesystem at the given mount point. * This method blocks until the mount succeeds or times out. * * @param fileSystemRoot Path to the directory which will be the content root of the mounted drive. @@ -63,11 +63,11 @@ public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, Stri TIMEOUT, ALLOC_UNIT_SIZE, SECTOR_SIZE); - return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions); + return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions, () -> {}); } /** - * Mounts a virtual drive at the given mount point containing contents of the given path with the specified additional mount options. + * Mounts the root of a filesystem at the given mount point with the specified additional mount options. * If an additional mount option is not specified the default value is used. * This method blocks until the mount succeeds or times out. * @@ -89,10 +89,37 @@ public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, Stri mountOptions.getTimeout().orElse(TIMEOUT), mountOptions.getAllocationUnitSize().orElse(ALLOC_UNIT_SIZE), mountOptions.getSectorSize().orElse(SECTOR_SIZE)); - return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions); + return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions, () -> {}); } - private Mount mount(Path fileSystemRoot, String volumeName, String fileSystemName, DeviceOptions deviceOptions) throws MountFailedException { + /** + * Mounts the root of a filesystem at the given mount point with the specified additional mount options and an action which is executed after unmount. + * If an additional mount option is not specified the default value is used. + * This method blocks until the mount succeeds or times out. + * + * @param fileSystemRoot Path to the directory which will be the content root of the mounted drive. + * @param mountPoint The mount point of the mounted drive. Can be an empty directory or a drive letter. + * @param volumeName The name of the drive as shown to the user. + * @param fileSystemName The technical file system name shown in the drive properties window. + * @param additionalOptions Additional options for the mount. For any unset option a default value is used. See {@link MountUtil} for details. + * @param afterUnmountAction action to be performed after the filesystem is unmounted. + * @return The mount object. + * @throws MountFailedException if the mount process is aborted due to errors + */ + public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName, String additionalOptions, Runnable afterUnmountAction) throws MountFailedException { + var absMountPoint = mountPoint.toAbsolutePath(); + var mountOptions = parseMountOptions(additionalOptions); + DeviceOptions deviceOptions = new DeviceOptions(absMountPoint.toString(), + mountOptions.getThreadCount().orElse(THREAD_COUNT), + mountOptions.getDokanOptions(), + UNC_NAME, + mountOptions.getTimeout().orElse(TIMEOUT), + mountOptions.getAllocationUnitSize().orElse(ALLOC_UNIT_SIZE), + mountOptions.getSectorSize().orElse(SECTOR_SIZE)); + return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions, afterUnmountAction); + } + + private Mount mount(Path fileSystemRoot, String volumeName, String fileSystemName, DeviceOptions deviceOptions, Runnable afterUnmountAction) throws MountFailedException { VolumeInformation volumeInfo = new VolumeInformation(VolumeInformation.DEFAULT_MAX_COMPONENT_LENGTH, volumeName, 0x98765432, fileSystemName, FILE_SYSTEM_FEATURES); CompletableFuture mountDidSucceed = new CompletableFuture<>(); OpenHandleCheck.OpenHandleCheckBuilder handleCheckBuilder = OpenHandleCheck.getBuilder(); @@ -100,7 +127,7 @@ private Mount mount(Path fileSystemRoot, String volumeName, String fileSystemNam DokanyMount mount = new DokanyMount(deviceOptions, dokanyFs, handleCheckBuilder.build()); LOG.debug("Mounting on {}: ...", deviceOptions.MountPoint); try { - mount.mount(executorService); + mount.mount(executorService, afterUnmountAction); mountDidSucceed.get(MOUNT_TIMEOUT_MS, TimeUnit.MILLISECONDS); LOG.debug("Mounted directory at {} successfully.", deviceOptions.MountPoint); } catch (InterruptedException e) { diff --git a/src/test/java/org/cryptomator/frontend/dokany/ReadWriteMirrorTest.java b/src/test/java/org/cryptomator/frontend/dokany/ReadWriteMirrorTest.java index 575d1ec..3df469d 100644 --- a/src/test/java/org/cryptomator/frontend/dokany/ReadWriteMirrorTest.java +++ b/src/test/java/org/cryptomator/frontend/dokany/ReadWriteMirrorTest.java @@ -39,8 +39,9 @@ public static void main(String[] args) throws IOException, MountFailedException } } - MountFactory mountFactory = new MountFactory(Executors.newCachedThreadPool()); - try (Mount mount = mountFactory.mount(dirPath, mountPoint, "Test", "DokanyNioFS")) { + MountFactory mountFactory = new MountFactory(Executors.newSingleThreadExecutor()); + Runnable afterUnmountAction = () -> System.out.println("Volume unmounted"); + try (Mount mount = mountFactory.mount(dirPath, mountPoint, "Test", "DokanyNioFS", "--thread-count 5", afterUnmountAction)) { try { mount.reveal(new WindowsExplorerRevealer()); } catch (Exception e) { From c154b798ce30c5396ad900ff35277ceb8e21ecfc Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 25 Mar 2021 10:58:27 +0100 Subject: [PATCH 06/17] Throw IllegalStateException if reveal() is called on a not-mounted Mount object --- src/main/java/com/dokany/java/DokanyMount.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/dokany/java/DokanyMount.java b/src/main/java/com/dokany/java/DokanyMount.java index 7c429b8..75d9fc1 100644 --- a/src/main/java/com/dokany/java/DokanyMount.java +++ b/src/main/java/com/dokany/java/DokanyMount.java @@ -186,7 +186,11 @@ public void unmountForced() { @Override public void reveal(Revealer revealer) throws Exception { - revealer.reveal(Path.of(deviceOptions.MountPoint.toString())); + if (isMounted) { + revealer.reveal(Path.of(deviceOptions.MountPoint.toString())); + } else { + throw new IllegalStateException("Filesystem not mounted."); + } } } From e72e11c03a5240ba80b7fe43dc3460226f4513a9 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 25 Mar 2021 10:58:46 +0100 Subject: [PATCH 07/17] remove redundant log messages --- src/main/java/com/dokany/java/DokanyMount.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/dokany/java/DokanyMount.java b/src/main/java/com/dokany/java/DokanyMount.java index 75d9fc1..6806894 100644 --- a/src/main/java/com/dokany/java/DokanyMount.java +++ b/src/main/java/com/dokany/java/DokanyMount.java @@ -125,10 +125,8 @@ public synchronized void mount(Executor executor, Runnable afterUnmountAction) t //if the execution reaches this point, was directly unmounted. throw new DokanyException("Mount failed: Volume was instantly unmounted."); } catch (UnsatisfiedLinkError err) { - LOG.error("Unable to load dokan driver.", err); throw new DokanyException(err); } catch (ExecutionException e) { - LOG.error("Error while mounting", e.getCause()); if (e.getCause() instanceof DokanyException) { throw (DokanyException) e.getCause(); } else { From bbac276b9489f591d665fe98b85d0454b8c1ea90 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 25 Mar 2021 11:45:42 +0100 Subject: [PATCH 08/17] Refactor exception handling: * Rename MountFailedException to DokanyMountFailedException * Make DokanyException checked --- .../java/com/dokany/java/DokanyException.java | 6 +++- .../java/com/dokany/java/DokanyMount.java | 28 +++++++++++++++---- .../dokany/DokanyMountFailedException.java | 16 +++++++++++ .../frontend/dokany/MountFactory.java | 24 +++++++++------- .../frontend/dokany/MountFailedException.java | 12 -------- .../frontend/dokany/MultipleMirrorsTest.java | 4 +-- .../dokany/ReadWriteCryptoFsTest.java | 2 +- .../frontend/dokany/ReadWriteMirrorTest.java | 2 +- 8 files changed, 61 insertions(+), 33 deletions(-) create mode 100644 src/main/java/org/cryptomator/frontend/dokany/DokanyMountFailedException.java delete mode 100644 src/main/java/org/cryptomator/frontend/dokany/MountFailedException.java diff --git a/src/main/java/com/dokany/java/DokanyException.java b/src/main/java/com/dokany/java/DokanyException.java index 2ab3cc6..655cb34 100644 --- a/src/main/java/com/dokany/java/DokanyException.java +++ b/src/main/java/com/dokany/java/DokanyException.java @@ -1,6 +1,6 @@ package com.dokany.java; -public final class DokanyException extends RuntimeException { +public final class DokanyException extends Exception { public DokanyException(String msg) { super(msg); @@ -9,4 +9,8 @@ public DokanyException(String msg) { public DokanyException(Throwable e) { super(e); } + + DokanyException(String msg, Throwable cause){ + super(msg, cause); + } } \ No newline at end of file diff --git a/src/main/java/com/dokany/java/DokanyMount.java b/src/main/java/com/dokany/java/DokanyMount.java index 6806894..a239621 100644 --- a/src/main/java/com/dokany/java/DokanyMount.java +++ b/src/main/java/com/dokany/java/DokanyMount.java @@ -107,14 +107,14 @@ public synchronized void mount(Executor executor, Runnable afterUnmountAction) t //real mount op mountFuture = CompletableFuture .supplyAsync(() -> NativeMethods.DokanMain(deviceOptions, new DokanyOperationsProxy(fileSystem)), executor) - .handle((BiFunction) (returnVal, exception) -> { + .handle((BiFunction) (returnVal, throwable) -> { isMounted = false; afterUnmountAction.run(); - if (exception != null) { - throw new DokanyException(exception); + if (throwable != null) { + throw new DokanyRuntimeException(throwable); } if (returnVal != null && returnVal != 0) { - throw new DokanyException("Return Code " + returnVal + ": " + MountError.fromInt(returnVal).getDescription()); + throw new DokanyRuntimeException("Non-Zero return code " + returnVal + ": " + MountError.fromInt(returnVal).getDescription()); } return null; }); @@ -127,8 +127,8 @@ public synchronized void mount(Executor executor, Runnable afterUnmountAction) t } catch (UnsatisfiedLinkError err) { throw new DokanyException(err); } catch (ExecutionException e) { - if (e.getCause() instanceof DokanyException) { - throw (DokanyException) e.getCause(); + if (e.getCause() instanceof DokanyRuntimeException) { + throw new DokanyException(e.getMessage(), e.getCause()); } else { throw new DokanyException(e.getCause()); } @@ -191,4 +191,20 @@ public void reveal(Revealer revealer) throws Exception { } } + private static class DokanyRuntimeException extends RuntimeException { + + DokanyRuntimeException(String msg) { + super(msg); + } + + DokanyRuntimeException(Throwable cause) { + super(cause); + } + + DokanyRuntimeException(String msg, Throwable cause) { + super(msg, cause); + } + + } + } diff --git a/src/main/java/org/cryptomator/frontend/dokany/DokanyMountFailedException.java b/src/main/java/org/cryptomator/frontend/dokany/DokanyMountFailedException.java new file mode 100644 index 0000000..9bc8b72 --- /dev/null +++ b/src/main/java/org/cryptomator/frontend/dokany/DokanyMountFailedException.java @@ -0,0 +1,16 @@ +package org.cryptomator.frontend.dokany; + +public class DokanyMountFailedException extends Exception { + + public DokanyMountFailedException(String message) { + super(message); + } + + public DokanyMountFailedException(String msg, Throwable cause) { + super(msg, cause); + } + + public DokanyMountFailedException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java b/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java index 0dfb145..4da81c6 100644 --- a/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java +++ b/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java @@ -1,5 +1,6 @@ package org.cryptomator.frontend.dokany; +import com.dokany.java.DokanyException; import com.dokany.java.DokanyFileSystem; import com.dokany.java.DokanyMount; import com.dokany.java.constants.DokanOption; @@ -52,9 +53,9 @@ public MountFactory(ExecutorService executorService) { * @param volumeName The name of the drive as shown to the user. * @param fileSystemName The technical file system name shown in the drive properties window. * @return The mount object. - * @throws MountFailedException if the mount process is aborted due to errors + * @throws DokanyMountFailedException if the mount process is aborted due to errors */ - public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName) throws MountFailedException { + public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName) throws DokanyMountFailedException { var absMountPoint = mountPoint.toAbsolutePath(); DeviceOptions deviceOptions = new DeviceOptions(absMountPoint.toString(), THREAD_COUNT, @@ -77,9 +78,9 @@ public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, Stri * @param fileSystemName The technical file system name shown in the drive properties window. * @param additionalOptions Additional options for the mount. For any unset option a default value is used. See {@link MountUtil} for details. * @return The mount object. - * @throws MountFailedException if the mount process is aborted due to errors + * @throws DokanyMountFailedException if the mount process is aborted due to errors */ - public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName, String additionalOptions) throws MountFailedException { + public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName, String additionalOptions) throws DokanyMountFailedException { var absMountPoint = mountPoint.toAbsolutePath(); var mountOptions = parseMountOptions(additionalOptions); DeviceOptions deviceOptions = new DeviceOptions(absMountPoint.toString(), @@ -104,9 +105,9 @@ public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, Stri * @param additionalOptions Additional options for the mount. For any unset option a default value is used. See {@link MountUtil} for details. * @param afterUnmountAction action to be performed after the filesystem is unmounted. * @return The mount object. - * @throws MountFailedException if the mount process is aborted due to errors + * @throws DokanyMountFailedException if the mount process is aborted due to errors */ - public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName, String additionalOptions, Runnable afterUnmountAction) throws MountFailedException { + public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName, String additionalOptions, Runnable afterUnmountAction) throws DokanyMountFailedException { var absMountPoint = mountPoint.toAbsolutePath(); var mountOptions = parseMountOptions(additionalOptions); DeviceOptions deviceOptions = new DeviceOptions(absMountPoint.toString(), @@ -119,7 +120,7 @@ public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, Stri return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions, afterUnmountAction); } - private Mount mount(Path fileSystemRoot, String volumeName, String fileSystemName, DeviceOptions deviceOptions, Runnable afterUnmountAction) throws MountFailedException { + private Mount mount(Path fileSystemRoot, String volumeName, String fileSystemName, DeviceOptions deviceOptions, Runnable afterUnmountAction) throws DokanyMountFailedException { VolumeInformation volumeInfo = new VolumeInformation(VolumeInformation.DEFAULT_MAX_COMPONENT_LENGTH, volumeName, 0x98765432, fileSystemName, FILE_SYSTEM_FEATURES); CompletableFuture mountDidSucceed = new CompletableFuture<>(); OpenHandleCheck.OpenHandleCheckBuilder handleCheckBuilder = OpenHandleCheck.getBuilder(); @@ -134,19 +135,22 @@ private Mount mount(Path fileSystemRoot, String volumeName, String fileSystemNam Thread.currentThread().interrupt(); } catch (ExecutionException e) { LOG.error("Mounting failed.", e); - throw new MountFailedException(e.getCause()); + throw new DokanyMountFailedException(e.getCause()); } catch (TimeoutException e) { LOG.warn("Mounting timed out."); + } catch (DokanyException e) { + LOG.error("Mounting failed.", e); + throw new DokanyMountFailedException("Error while mounting.", e); } return mount; } - private MountUtil.MountOptions parseMountOptions(String options) throws MountFailedException { + private MountUtil.MountOptions parseMountOptions(String options) throws DokanyMountFailedException { try { return MountUtil.parse(options); } catch (IllegalArgumentException | ParseException e) { - throw new MountFailedException(e); + throw new DokanyMountFailedException("Unable to parse mount options", e); } } diff --git a/src/main/java/org/cryptomator/frontend/dokany/MountFailedException.java b/src/main/java/org/cryptomator/frontend/dokany/MountFailedException.java deleted file mode 100644 index cb45bf0..0000000 --- a/src/main/java/org/cryptomator/frontend/dokany/MountFailedException.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.cryptomator.frontend.dokany; - -public class MountFailedException extends Exception{ - - public MountFailedException(String message) { - super(message); - } - - public MountFailedException(Throwable cause) { - super(cause); - } -} diff --git a/src/test/java/org/cryptomator/frontend/dokany/MultipleMirrorsTest.java b/src/test/java/org/cryptomator/frontend/dokany/MultipleMirrorsTest.java index f389794..b070d80 100644 --- a/src/test/java/org/cryptomator/frontend/dokany/MultipleMirrorsTest.java +++ b/src/test/java/org/cryptomator/frontend/dokany/MultipleMirrorsTest.java @@ -69,7 +69,7 @@ public void testMultipleConcurrentMirrorsFolderMounts() { MountFactory mountFactory = new MountFactory(mountThreads); var mount = mountFactory.mount(testDir, mountPoint, "mnt" + i, "Mirror FS"); mounts.add(mount); - } catch (MountFailedException e) { + } catch (DokanyMountFailedException e) { e.printStackTrace(); } } @@ -88,7 +88,7 @@ public void testMultipleConcurrentMirrorsDriveLetterMounts() { MountFactory mountFactory = new MountFactory(mountThreads); var mount = mountFactory.mount(testDir, mountPoint, "mnt" + i, "Mirror FS"); mounts.add(mount); - } catch (MountFailedException e) { + } catch (DokanyMountFailedException e) { e.printStackTrace(); } } diff --git a/src/test/java/org/cryptomator/frontend/dokany/ReadWriteCryptoFsTest.java b/src/test/java/org/cryptomator/frontend/dokany/ReadWriteCryptoFsTest.java index 6584a19..55bcf2c 100644 --- a/src/test/java/org/cryptomator/frontend/dokany/ReadWriteCryptoFsTest.java +++ b/src/test/java/org/cryptomator/frontend/dokany/ReadWriteCryptoFsTest.java @@ -20,7 +20,7 @@ public class ReadWriteCryptoFsTest { System.setProperty(SimpleLogger.DATE_TIME_FORMAT_KEY, "HH:mm:ss:SSS"); } - public static void main(String[] args) throws IOException, MountFailedException { + public static void main(String[] args) throws IOException, DokanyMountFailedException { if (!MountFactory.isApplicable()) { System.err.println("Dokany not installed."); return; diff --git a/src/test/java/org/cryptomator/frontend/dokany/ReadWriteMirrorTest.java b/src/test/java/org/cryptomator/frontend/dokany/ReadWriteMirrorTest.java index 3df469d..8d1bff1 100644 --- a/src/test/java/org/cryptomator/frontend/dokany/ReadWriteMirrorTest.java +++ b/src/test/java/org/cryptomator/frontend/dokany/ReadWriteMirrorTest.java @@ -17,7 +17,7 @@ public class ReadWriteMirrorTest { System.setProperty(SimpleLogger.DATE_TIME_FORMAT_KEY, "HH:mm:ss:SSS"); } - public static void main(String[] args) throws IOException, MountFailedException { + public static void main(String[] args) throws IOException, DokanyMountFailedException { if (!MountFactory.isApplicable()) { System.err.println("Dokany not installed."); return; From 858febd6a362c75a90cbe90ebee57f0037657ecd Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 25 Mar 2021 12:21:28 +0100 Subject: [PATCH 09/17] fixing integration test --- .../cryptomator/frontend/dokany/MirrorReadOnlyThread.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/cryptomator/frontend/dokany/MirrorReadOnlyThread.java b/src/test/java/org/cryptomator/frontend/dokany/MirrorReadOnlyThread.java index 7b3e4cc..ec84c58 100644 --- a/src/test/java/org/cryptomator/frontend/dokany/MirrorReadOnlyThread.java +++ b/src/test/java/org/cryptomator/frontend/dokany/MirrorReadOnlyThread.java @@ -1,5 +1,6 @@ package org.cryptomator.frontend.dokany; +import com.dokany.java.DokanyException; import com.dokany.java.DokanyMount; import com.dokany.java.DokanyFileSystem; import com.dokany.java.constants.FileSystemFeature; @@ -48,8 +49,12 @@ public MirrorReadOnlyThread(Path dirToMirror, Path mountPoint) { @Override public void run() { - dokany.mount(); System.out.println("Starting new dokany thread with mount point " + mountPoint.toString()); + try { + dokany.mount(); + } catch (DokanyException e) { + e.printStackTrace(); + } } public DokanyMount getDokanyDriver() { From 25ac3a43d67126f357546ce8faffe93c60521a8a Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 25 Mar 2021 17:01:55 +0100 Subject: [PATCH 10/17] clarify parameter in in MountFactory.mount(...) --- .../org/cryptomator/frontend/dokany/MountFactory.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java b/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java index 4da81c6..54a015a 100644 --- a/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java +++ b/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java @@ -103,11 +103,11 @@ public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, Stri * @param volumeName The name of the drive as shown to the user. * @param fileSystemName The technical file system name shown in the drive properties window. * @param additionalOptions Additional options for the mount. For any unset option a default value is used. See {@link MountUtil} for details. - * @param afterUnmountAction action to be performed after the filesystem is unmounted. + * @param afterMountExitAction action to be performed after the dokan mount exited (regularly or irregularly) * @return The mount object. * @throws DokanyMountFailedException if the mount process is aborted due to errors */ - public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName, String additionalOptions, Runnable afterUnmountAction) throws DokanyMountFailedException { + public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName, String additionalOptions, Runnable afterMountExitAction) throws DokanyMountFailedException { var absMountPoint = mountPoint.toAbsolutePath(); var mountOptions = parseMountOptions(additionalOptions); DeviceOptions deviceOptions = new DeviceOptions(absMountPoint.toString(), @@ -117,10 +117,10 @@ public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, Stri mountOptions.getTimeout().orElse(TIMEOUT), mountOptions.getAllocationUnitSize().orElse(ALLOC_UNIT_SIZE), mountOptions.getSectorSize().orElse(SECTOR_SIZE)); - return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions, afterUnmountAction); + return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions, afterMountExitAction); } - private Mount mount(Path fileSystemRoot, String volumeName, String fileSystemName, DeviceOptions deviceOptions, Runnable afterUnmountAction) throws DokanyMountFailedException { + private Mount mount(Path fileSystemRoot, String volumeName, String fileSystemName, DeviceOptions deviceOptions, Runnable afterMountExitAction) throws DokanyMountFailedException { VolumeInformation volumeInfo = new VolumeInformation(VolumeInformation.DEFAULT_MAX_COMPONENT_LENGTH, volumeName, 0x98765432, fileSystemName, FILE_SYSTEM_FEATURES); CompletableFuture mountDidSucceed = new CompletableFuture<>(); OpenHandleCheck.OpenHandleCheckBuilder handleCheckBuilder = OpenHandleCheck.getBuilder(); @@ -128,7 +128,7 @@ private Mount mount(Path fileSystemRoot, String volumeName, String fileSystemNam DokanyMount mount = new DokanyMount(deviceOptions, dokanyFs, handleCheckBuilder.build()); LOG.debug("Mounting on {}: ...", deviceOptions.MountPoint); try { - mount.mount(executorService, afterUnmountAction); + mount.mount(executorService, afterMountExitAction); mountDidSucceed.get(MOUNT_TIMEOUT_MS, TimeUnit.MILLISECONDS); LOG.debug("Mounted directory at {} successfully.", deviceOptions.MountPoint); } catch (InterruptedException e) { From 7f6bb355859b489bdbb8832464a27a677bde769f Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 25 Mar 2021 17:08:09 +0100 Subject: [PATCH 11/17] Clean up and revert a change made in bbac276b9489f591d665fe98b85d0454b8c1ea90: * DokanyException extends RuntimeException again --- .../java/com/dokany/java/DokanyException.java | 4 +- .../java/com/dokany/java/DokanyMount.java | 51 +++++-------------- 2 files changed, 16 insertions(+), 39 deletions(-) diff --git a/src/main/java/com/dokany/java/DokanyException.java b/src/main/java/com/dokany/java/DokanyException.java index 655cb34..08e2d96 100644 --- a/src/main/java/com/dokany/java/DokanyException.java +++ b/src/main/java/com/dokany/java/DokanyException.java @@ -1,6 +1,6 @@ package com.dokany.java; -public final class DokanyException extends Exception { +public final class DokanyException extends RuntimeException { public DokanyException(String msg) { super(msg); @@ -10,7 +10,7 @@ public DokanyException(Throwable e) { super(e); } - DokanyException(String msg, Throwable cause){ + DokanyException(String msg, Throwable cause) { super(msg, cause); } } \ No newline at end of file diff --git a/src/main/java/com/dokany/java/DokanyMount.java b/src/main/java/com/dokany/java/DokanyMount.java index a239621..4ec8e41 100644 --- a/src/main/java/com/dokany/java/DokanyMount.java +++ b/src/main/java/com/dokany/java/DokanyMount.java @@ -15,7 +15,6 @@ import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.function.BiFunction; /** * Main class to start and stop Dokany file system. @@ -29,7 +28,6 @@ public final class DokanyMount implements Mount { private final SafeUnmountCheck unmountCheck; private volatile boolean isMounted; - private volatile CompletableFuture mountFuture; public DokanyMount(final DeviceOptions deviceOptions, final DokanyFileSystem fileSystem) { this(deviceOptions, fileSystem, () -> true); @@ -38,7 +36,6 @@ public DokanyMount(final DeviceOptions deviceOptions, final DokanyFileSystem fil public DokanyMount(final DeviceOptions deviceOptions, final DokanyFileSystem fileSystem, SafeUnmountCheck unmountCheck) { this.deviceOptions = deviceOptions; this.fileSystem = fileSystem; - this.mountFuture = CompletableFuture.failedFuture(new IllegalStateException("Not mounted.")); this.isMounted = false; this.unmountCheck = unmountCheck; } @@ -69,7 +66,7 @@ public long getVersion() { *

* Additionally a shutdown hook invoking {@link #close()} is registered to the JVM. */ - public synchronized void mount() throws DokanyException { + public void mount() throws DokanyException { this.mount(ForkJoinPool.commonPool(), () -> {}); } @@ -83,7 +80,7 @@ public synchronized void mount() throws DokanyException { * @param executor * @throws DokanyException */ - public synchronized void mount(Executor executor) throws DokanyException { + public void mount(Executor executor) throws DokanyException { this.mount(executor, () -> {}); } @@ -95,9 +92,9 @@ public synchronized void mount(Executor executor) throws DokanyException { * Additionally a shutdown hook invoking {@link #close()} is registered to the JVM. * * @param executor - * @param afterUnmountAction object with a run() method which is executed after unmount + * @param afterExitAction object with a run() method which is executed after the mount process exited */ - public synchronized void mount(Executor executor, Runnable afterUnmountAction) throws DokanyException { + public synchronized void mount(Executor executor, Runnable afterExitAction) throws DokanyException { if (!isMounted) { isMounted = true; try { @@ -105,30 +102,26 @@ public synchronized void mount(Executor executor, Runnable afterUnmountAction) t LOG.info("Dokany API/driver version: {} / {}", getVersion(), getDriverVersion()); //real mount op - mountFuture = CompletableFuture - .supplyAsync(() -> NativeMethods.DokanMain(deviceOptions, new DokanyOperationsProxy(fileSystem)), executor) - .handle((BiFunction) (returnVal, throwable) -> { + CompletableFuture.supplyAsync(() -> NativeMethods.DokanMain(deviceOptions, new DokanyOperationsProxy(fileSystem)), executor) + .whenComplete((returnVal, throwable) -> { isMounted = false; - afterUnmountAction.run(); + afterExitAction.run(); if (throwable != null) { - throw new DokanyRuntimeException(throwable); + throw new DokanyException(throwable); } if (returnVal != null && returnVal != 0) { - throw new DokanyRuntimeException("Non-Zero return code " + returnVal + ": " + MountError.fromInt(returnVal).getDescription()); + throw new DokanyException("Non-Zero return code " + returnVal + ": " + MountError.fromInt(returnVal).getDescription()); } - return null; - }); + }) + .get(1000, TimeUnit.MILLISECONDS); - //check if mount runs - mountFuture.get(1000, TimeUnit.MILLISECONDS); - - //if the execution reaches this point, was directly unmounted. + //if the execution reaches this point, device was directly unmounted. throw new DokanyException("Mount failed: Volume was instantly unmounted."); } catch (UnsatisfiedLinkError err) { throw new DokanyException(err); } catch (ExecutionException e) { - if (e.getCause() instanceof DokanyRuntimeException) { - throw new DokanyException(e.getMessage(), e.getCause()); + if (e.getCause() instanceof DokanyException) { + throw (DokanyException) e.getCause(); } else { throw new DokanyException(e.getCause()); } @@ -191,20 +184,4 @@ public void reveal(Revealer revealer) throws Exception { } } - private static class DokanyRuntimeException extends RuntimeException { - - DokanyRuntimeException(String msg) { - super(msg); - } - - DokanyRuntimeException(Throwable cause) { - super(cause); - } - - DokanyRuntimeException(String msg, Throwable cause) { - super(msg, cause); - } - - } - } From 8045576113b056816cc724cb2eaa029804731bd9 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 29 Mar 2021 10:59:31 +0200 Subject: [PATCH 12/17] change wording/ rename parameter --- src/main/java/com/dokany/java/DokanyMount.java | 6 +++--- .../org/cryptomator/frontend/dokany/MountFactory.java | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/dokany/java/DokanyMount.java b/src/main/java/com/dokany/java/DokanyMount.java index 4ec8e41..68b0cc2 100644 --- a/src/main/java/com/dokany/java/DokanyMount.java +++ b/src/main/java/com/dokany/java/DokanyMount.java @@ -92,9 +92,9 @@ public void mount(Executor executor) throws DokanyException { * Additionally a shutdown hook invoking {@link #close()} is registered to the JVM. * * @param executor - * @param afterExitAction object with a run() method which is executed after the mount process exited + * @param onDokanExit object with a run() method which is executed after the Dokan process exited */ - public synchronized void mount(Executor executor, Runnable afterExitAction) throws DokanyException { + public synchronized void mount(Executor executor, Runnable onDokanExit) throws DokanyException { if (!isMounted) { isMounted = true; try { @@ -105,7 +105,7 @@ public synchronized void mount(Executor executor, Runnable afterExitAction) thro CompletableFuture.supplyAsync(() -> NativeMethods.DokanMain(deviceOptions, new DokanyOperationsProxy(fileSystem)), executor) .whenComplete((returnVal, throwable) -> { isMounted = false; - afterExitAction.run(); + onDokanExit.run(); if (throwable != null) { throw new DokanyException(throwable); } diff --git a/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java b/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java index 54a015a..75410c6 100644 --- a/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java +++ b/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java @@ -103,11 +103,11 @@ public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, Stri * @param volumeName The name of the drive as shown to the user. * @param fileSystemName The technical file system name shown in the drive properties window. * @param additionalOptions Additional options for the mount. For any unset option a default value is used. See {@link MountUtil} for details. - * @param afterMountExitAction action to be performed after the dokan mount exited (regularly or irregularly) + * @param onDokanExit action to be performed after the dokan mount exited (regularly or irregularly) * @return The mount object. * @throws DokanyMountFailedException if the mount process is aborted due to errors */ - public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName, String additionalOptions, Runnable afterMountExitAction) throws DokanyMountFailedException { + public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName, String additionalOptions, Runnable onDokanExit) throws DokanyMountFailedException { var absMountPoint = mountPoint.toAbsolutePath(); var mountOptions = parseMountOptions(additionalOptions); DeviceOptions deviceOptions = new DeviceOptions(absMountPoint.toString(), @@ -117,10 +117,10 @@ public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, Stri mountOptions.getTimeout().orElse(TIMEOUT), mountOptions.getAllocationUnitSize().orElse(ALLOC_UNIT_SIZE), mountOptions.getSectorSize().orElse(SECTOR_SIZE)); - return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions, afterMountExitAction); + return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions, onDokanExit); } - private Mount mount(Path fileSystemRoot, String volumeName, String fileSystemName, DeviceOptions deviceOptions, Runnable afterMountExitAction) throws DokanyMountFailedException { + private Mount mount(Path fileSystemRoot, String volumeName, String fileSystemName, DeviceOptions deviceOptions, Runnable onDokanExit) throws DokanyMountFailedException { VolumeInformation volumeInfo = new VolumeInformation(VolumeInformation.DEFAULT_MAX_COMPONENT_LENGTH, volumeName, 0x98765432, fileSystemName, FILE_SYSTEM_FEATURES); CompletableFuture mountDidSucceed = new CompletableFuture<>(); OpenHandleCheck.OpenHandleCheckBuilder handleCheckBuilder = OpenHandleCheck.getBuilder(); @@ -128,7 +128,7 @@ private Mount mount(Path fileSystemRoot, String volumeName, String fileSystemNam DokanyMount mount = new DokanyMount(deviceOptions, dokanyFs, handleCheckBuilder.build()); LOG.debug("Mounting on {}: ...", deviceOptions.MountPoint); try { - mount.mount(executorService, afterMountExitAction); + mount.mount(executorService, onDokanExit); mountDidSucceed.get(MOUNT_TIMEOUT_MS, TimeUnit.MILLISECONDS); LOG.debug("Mounted directory at {} successfully.", deviceOptions.MountPoint); } catch (InterruptedException e) { From 885cc635bf80631da35fe678ca655160d3fe7795 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 30 Mar 2021 19:18:54 +0200 Subject: [PATCH 13/17] Refactoring mount method: * remove executor variable from MountFactory * make MountFactory methods static * DokanMount.mount() spawns always a new thread and throws checked exception DokanyException * change type of onDokanExit from Runnable to Consumer to differentiate between regular and irregular failures --- .../java/com/dokany/java/DokanyException.java | 2 +- .../java/com/dokany/java/DokanyMount.java | 113 +++++++++--------- .../dokany/java/DokanyOperationsProxy.java | 22 ++-- .../frontend/dokany/MountFactory.java | 37 ++---- .../frontend/dokany/ReadWriteAdapter.java | 9 +- .../frontend/dokany/MirrorReadOnlyThread.java | 2 +- .../frontend/dokany/MultipleMirrorsTest.java | 10 +- .../dokany/ReadWriteCryptoFsTest.java | 3 +- .../frontend/dokany/ReadWriteMirrorTest.java | 20 +++- 9 files changed, 102 insertions(+), 116 deletions(-) diff --git a/src/main/java/com/dokany/java/DokanyException.java b/src/main/java/com/dokany/java/DokanyException.java index 08e2d96..1fca9a9 100644 --- a/src/main/java/com/dokany/java/DokanyException.java +++ b/src/main/java/com/dokany/java/DokanyException.java @@ -1,6 +1,6 @@ package com.dokany.java; -public final class DokanyException extends RuntimeException { +public final class DokanyException extends Exception { public DokanyException(String msg) { super(msg); diff --git a/src/main/java/com/dokany/java/DokanyMount.java b/src/main/java/com/dokany/java/DokanyMount.java index 68b0cc2..7fd2686 100644 --- a/src/main/java/com/dokany/java/DokanyMount.java +++ b/src/main/java/com/dokany/java/DokanyMount.java @@ -9,12 +9,12 @@ import org.slf4j.LoggerFactory; import java.nio.file.Path; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; /** * Main class to start and stop Dokany file system. @@ -22,12 +22,14 @@ public final class DokanyMount implements Mount { private static final Logger LOG = LoggerFactory.getLogger(DokanyMount.class); + private static final int MOUNT_TIMEOUT_MILLIS = 3000; + private static final AtomicInteger mountCounter = new AtomicInteger(1); private final DeviceOptions deviceOptions; private final DokanyFileSystem fileSystem; private final SafeUnmountCheck unmountCheck; - private volatile boolean isMounted; + private final AtomicBoolean isMounted; public DokanyMount(final DeviceOptions deviceOptions, final DokanyFileSystem fileSystem) { this(deviceOptions, fileSystem, () -> true); @@ -36,7 +38,7 @@ public DokanyMount(final DeviceOptions deviceOptions, final DokanyFileSystem fil public DokanyMount(final DeviceOptions deviceOptions, final DokanyFileSystem fileSystem, SafeUnmountCheck unmountCheck) { this.deviceOptions = deviceOptions; this.fileSystem = fileSystem; - this.isMounted = false; + this.isMounted = new AtomicBoolean(false); this.unmountCheck = unmountCheck; } @@ -61,90 +63,83 @@ public long getVersion() { } /** - * Calls {@link NativeMethods#DokanMain(DeviceOptions, DokanyOperations)} in a thread of the Common ForkJoin Pool. + * Calls {@link NativeMethods#DokanMain(DeviceOptions, DokanyOperations)} in a new thread. * No-op if this object is already mounted. *

* Additionally a shutdown hook invoking {@link #close()} is registered to the JVM. */ public void mount() throws DokanyException { - this.mount(ForkJoinPool.commonPool(), () -> {}); + this.mount(ignored -> {}); } - /** - * Calls {@link NativeMethods#DokanMain(DeviceOptions, DokanyOperations)} on the given executor. + * Calls {@link NativeMethods#DokanMain(DeviceOptions, DokanyOperations)} in a new thread and after DokanMain exit runs the specified action with an throwable as parameter if DokanMain terminiated irregularly. * No-op if this object is already mounted. *

* Additionally a shutdown hook invoking {@link #close()} is registered to the JVM. * - * @param executor - * @throws DokanyException - */ - public void mount(Executor executor) throws DokanyException { - this.mount(executor, () -> {}); - } - - - /** - * Calls {@link NativeMethods#DokanMain(DeviceOptions, DokanyOperations)} on the given executor and runs after mount termination a specified action. - * No-op if this object is already mounted. - *

- * Additionally a shutdown hook invoking {@link #close()} is registered to the JVM. - * - * @param executor * @param onDokanExit object with a run() method which is executed after the Dokan process exited */ - public synchronized void mount(Executor executor, Runnable onDokanExit) throws DokanyException { - if (!isMounted) { - isMounted = true; + public synchronized void mount(Consumer onDokanExit) throws DokanyException { + if (!isMounted.getAndSet(true)) { + int mountId = mountCounter.getAndIncrement(); try { Runtime.getRuntime().addShutdownHook(new Thread(this::close)); LOG.info("Dokany API/driver version: {} / {}", getVersion(), getDriverVersion()); + //real mount op - CompletableFuture.supplyAsync(() -> NativeMethods.DokanMain(deviceOptions, new DokanyOperationsProxy(fileSystem)), executor) - .whenComplete((returnVal, throwable) -> { - isMounted = false; - onDokanExit.run(); - if (throwable != null) { - throw new DokanyException(throwable); - } - if (returnVal != null && returnVal != 0) { - throw new DokanyException("Non-Zero return code " + returnVal + ": " + MountError.fromInt(returnVal).getDescription()); - } - }) - .get(1000, TimeUnit.MILLISECONDS); - - //if the execution reaches this point, device was directly unmounted. - throw new DokanyException("Mount failed: Volume was instantly unmounted."); + CountDownLatch mountSuccessSignal = new CountDownLatch(1); + AtomicReference exception = new AtomicReference<>(); + var mountThread = new Thread(() -> { + try { + int r = NativeMethods.DokanMain(deviceOptions, new DokanyOperationsProxy(mountSuccessSignal, fileSystem, "dokanMount-" + mountId + "-callback-")); + if (r != 0) { + throw new DokanyException("DokanMain returned error code" + r + ": " + MountError.fromInt(r).getDescription()); + } + } catch (Exception e) { + exception.set(e); + } finally { + isMounted.set(false); + onDokanExit.accept(exception.get()); + } + }); + mountThread.setName("dokanMount-" + mountId + "-main"); + mountThread.setDaemon(true); + mountThread.start(); + + // wait for mounted() is called, unlocking the barrier + if (!mountSuccessSignal.await(MOUNT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { + if (exception.get() != null) { + if( exception.get() instanceof DokanyException) { + throw (DokanyException) exception.get(); + } else { + throw new DokanyException(exception.get()); + } + } + throw new DokanyException("Mount timed out"); + } + } catch (UnsatisfiedLinkError err) { throw new DokanyException(err); - } catch (ExecutionException e) { - if (e.getCause() instanceof DokanyException) { - throw (DokanyException) e.getCause(); - } else { - throw new DokanyException(e.getCause()); - } } catch (InterruptedException e) { Thread.currentThread().interrupt(); - } catch (TimeoutException e) { - //up and running + throw new DokanyException("Mount interrupted."); } } else { LOG.debug("Dokan Device already mounted on {}.", deviceOptions.MountPoint); } } - /** - * Unmounts the Dokan Device from the mount point given in the mount options. + * Unmounts the Dokan device from its mount point. No-op if the device is not mounted. */ public synchronized void close() { - if (isMounted) { - LOG.info("Unmounting Dokan device at {}", deviceOptions.MountPoint); - boolean unmounted = NativeMethods.DokanRemoveMountPoint(deviceOptions.MountPoint); + if (isMounted.get()) { + LOG.info("Unmounting dokan device at {}", deviceOptions.MountPoint); + var unmounted = NativeMethods.DokanRemoveMountPoint(deviceOptions.MountPoint); if (unmounted) { - isMounted = false; + isMounted.set(false); } else { LOG.error("Unable to unmount dokan device at {}.", deviceOptions.MountPoint); } @@ -177,7 +172,7 @@ public void unmountForced() { @Override public void reveal(Revealer revealer) throws Exception { - if (isMounted) { + if (isMounted.get()) { revealer.reveal(Path.of(deviceOptions.MountPoint.toString())); } else { throw new IllegalStateException("Filesystem not mounted."); diff --git a/src/main/java/com/dokany/java/DokanyOperationsProxy.java b/src/main/java/com/dokany/java/DokanyOperationsProxy.java index 6f413c8..7b855ed 100644 --- a/src/main/java/com/dokany/java/DokanyOperationsProxy.java +++ b/src/main/java/com/dokany/java/DokanyOperationsProxy.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; /** @@ -26,17 +27,21 @@ final class DokanyOperationsProxy extends com.dokany.java.DokanyOperations { private final static Logger LOG = LoggerFactory.getLogger(DokanyOperationsProxy.class); - private final static String DEFAULT_THREAD_NAME = "DOKAN"; - + private final CountDownLatch mountSuccessSignaler; private final DokanyFileSystem fileSystem; - private final ThreadGroup nativeDokanThreads = new ThreadGroup("AttachedDokanThreads"); - private final CallbackThreadInitializer DEFAULT_CALLBACK_THREAD_INITIALIZER = new CallbackThreadInitializer(true, false, DEFAULT_THREAD_NAME, nativeDokanThreads); + private final String nativeThreadNamePrefix; + private final ThreadGroup nativeThreadGroup; + private final CallbackThreadInitializer callbackThreadInitializer; private final AtomicInteger threadCounter = new AtomicInteger(0); private final List usedCallbacks = new ArrayList<>(); - DokanyOperationsProxy(final DokanyFileSystem fileSystem) { + DokanyOperationsProxy(CountDownLatch mountSuccessSignaler, final DokanyFileSystem fileSystem, String nativeThreadNamePrefix) { + this.mountSuccessSignaler = mountSuccessSignaler; this.fileSystem = fileSystem; + this.nativeThreadNamePrefix = nativeThreadNamePrefix; + this.nativeThreadGroup = new ThreadGroup(nativeThreadNamePrefix); + this.callbackThreadInitializer = new CallbackThreadInitializer(true, false, nativeThreadNamePrefix, nativeThreadGroup); super.ZwCreateFile = new ZwCreateFileProxy(); usedCallbacks.add(super.ZwCreateFile); @@ -112,7 +117,7 @@ final class DokanyOperationsProxy extends com.dokany.java.DokanyOperations { super.FindStreams = null; //callbacks.add(super.FindStreams); - usedCallbacks.forEach(callback -> Native.setCallbackThreadInitializer(callback, DEFAULT_CALLBACK_THREAD_INITIALIZER)); + usedCallbacks.forEach(callback -> Native.setCallbackThreadInitializer(callback, callbackThreadInitializer)); } class ZwCreateFileProxy implements ZwCreateFile { @@ -121,8 +126,8 @@ class ZwCreateFileProxy implements ZwCreateFile { public long callback(WString rawPath, WinBase.SECURITY_ATTRIBUTES securityContext, int rawDesiredAccess, int rawFileAttributes, int rawShareAccess, int rawCreateDisposition, int rawCreateOptions, DokanyFileInfo dokanyFileInfo) { try { //hack to uniquely identify the dokan threads - if (Thread.currentThread().getName().equals(DEFAULT_THREAD_NAME)) { - Thread.currentThread().setName("dokan-" + threadCounter.getAndIncrement()); + if (Thread.currentThread().getName().equals(nativeThreadNamePrefix)) { + Thread.currentThread().setName(nativeThreadNamePrefix + threadCounter.getAndIncrement()); } int win32ErrorCode = fileSystem.zwCreateFile(rawPath, securityContext, rawDesiredAccess, rawFileAttributes, rawShareAccess, rawCreateDisposition, rawCreateOptions, dokanyFileInfo); @@ -372,6 +377,7 @@ class MountedProxy implements Mounted { @Override public long mounted(DokanyFileInfo dokanyFileInfo) { + mountSuccessSignaler.countDown(); try { return NativeMethods.DokanNtStatusFromWin32(fileSystem.mounted(dokanyFileInfo)); } catch (Exception e) { diff --git a/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java b/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java index 75410c6..ff4858a 100644 --- a/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java +++ b/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java @@ -21,11 +21,11 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; public class MountFactory { private static final Logger LOG = LoggerFactory.getLogger(MountFactory.class); - private static final int MOUNT_TIMEOUT_MS = 5000; private static final short THREAD_COUNT = 5; private static final String UNC_NAME = ""; private static final int TIMEOUT = 10000; @@ -38,11 +38,7 @@ public class MountFactory { // FileSystemFeature.SUPPORTS_REMOTE_STORAGE, // FileSystemFeature.UNICODE_ON_DISK); - private final ExecutorService executorService; - - public MountFactory(ExecutorService executorService) { - this.executorService = executorService; - } + private MountFactory() {} /** * Mounts the root of a filesystem at the given mount point. @@ -55,7 +51,7 @@ public MountFactory(ExecutorService executorService) { * @return The mount object. * @throws DokanyMountFailedException if the mount process is aborted due to errors */ - public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName) throws DokanyMountFailedException { + public static Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName) throws DokanyMountFailedException { var absMountPoint = mountPoint.toAbsolutePath(); DeviceOptions deviceOptions = new DeviceOptions(absMountPoint.toString(), THREAD_COUNT, @@ -64,7 +60,7 @@ public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, Stri TIMEOUT, ALLOC_UNIT_SIZE, SECTOR_SIZE); - return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions, () -> {}); + return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions, ignored -> {}); } /** @@ -80,7 +76,7 @@ public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, Stri * @return The mount object. * @throws DokanyMountFailedException if the mount process is aborted due to errors */ - public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName, String additionalOptions) throws DokanyMountFailedException { + public static Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName, String additionalOptions) throws DokanyMountFailedException { var absMountPoint = mountPoint.toAbsolutePath(); var mountOptions = parseMountOptions(additionalOptions); DeviceOptions deviceOptions = new DeviceOptions(absMountPoint.toString(), @@ -90,7 +86,7 @@ public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, Stri mountOptions.getTimeout().orElse(TIMEOUT), mountOptions.getAllocationUnitSize().orElse(ALLOC_UNIT_SIZE), mountOptions.getSectorSize().orElse(SECTOR_SIZE)); - return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions, () -> {}); + return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions, ignored -> {}); } /** @@ -103,11 +99,11 @@ public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, Stri * @param volumeName The name of the drive as shown to the user. * @param fileSystemName The technical file system name shown in the drive properties window. * @param additionalOptions Additional options for the mount. For any unset option a default value is used. See {@link MountUtil} for details. - * @param onDokanExit action to be performed after the dokan mount exited (regularly or irregularly) + * @param onDokanExit consumer which runs after the dokan mount exited. If the exit was irregularly, the Throwable parameter is not null. * @return The mount object. * @throws DokanyMountFailedException if the mount process is aborted due to errors */ - public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName, String additionalOptions, Runnable onDokanExit) throws DokanyMountFailedException { + public static Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName, String additionalOptions, Consumer onDokanExit) throws DokanyMountFailedException { var absMountPoint = mountPoint.toAbsolutePath(); var mountOptions = parseMountOptions(additionalOptions); DeviceOptions deviceOptions = new DeviceOptions(absMountPoint.toString(), @@ -120,24 +116,15 @@ public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, Stri return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions, onDokanExit); } - private Mount mount(Path fileSystemRoot, String volumeName, String fileSystemName, DeviceOptions deviceOptions, Runnable onDokanExit) throws DokanyMountFailedException { + private static Mount mount(Path fileSystemRoot, String volumeName, String fileSystemName, DeviceOptions deviceOptions, Consumer onDokanExit) throws DokanyMountFailedException { VolumeInformation volumeInfo = new VolumeInformation(VolumeInformation.DEFAULT_MAX_COMPONENT_LENGTH, volumeName, 0x98765432, fileSystemName, FILE_SYSTEM_FEATURES); - CompletableFuture mountDidSucceed = new CompletableFuture<>(); OpenHandleCheck.OpenHandleCheckBuilder handleCheckBuilder = OpenHandleCheck.getBuilder(); - DokanyFileSystem dokanyFs = new ReadWriteAdapter(fileSystemRoot, new LockManager(), volumeInfo, mountDidSucceed, handleCheckBuilder); + DokanyFileSystem dokanyFs = new ReadWriteAdapter(fileSystemRoot, new LockManager(), volumeInfo, handleCheckBuilder); DokanyMount mount = new DokanyMount(deviceOptions, dokanyFs, handleCheckBuilder.build()); LOG.debug("Mounting on {}: ...", deviceOptions.MountPoint); try { - mount.mount(executorService, onDokanExit); - mountDidSucceed.get(MOUNT_TIMEOUT_MS, TimeUnit.MILLISECONDS); + mount.mount(onDokanExit); LOG.debug("Mounted directory at {} successfully.", deviceOptions.MountPoint); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (ExecutionException e) { - LOG.error("Mounting failed.", e); - throw new DokanyMountFailedException(e.getCause()); - } catch (TimeoutException e) { - LOG.warn("Mounting timed out."); } catch (DokanyException e) { LOG.error("Mounting failed.", e); throw new DokanyMountFailedException("Error while mounting.", e); @@ -146,7 +133,7 @@ private Mount mount(Path fileSystemRoot, String volumeName, String fileSystemNam } - private MountUtil.MountOptions parseMountOptions(String options) throws DokanyMountFailedException { + private static MountUtil.MountOptions parseMountOptions(String options) throws DokanyMountFailedException { try { return MountUtil.parse(options); } catch (IllegalArgumentException | ParseException e) { diff --git a/src/main/java/org/cryptomator/frontend/dokany/ReadWriteAdapter.java b/src/main/java/org/cryptomator/frontend/dokany/ReadWriteAdapter.java index 93e47f7..de18bc5 100644 --- a/src/main/java/org/cryptomator/frontend/dokany/ReadWriteAdapter.java +++ b/src/main/java/org/cryptomator/frontend/dokany/ReadWriteAdapter.java @@ -68,15 +68,13 @@ public class ReadWriteAdapter implements DokanyFileSystem { private final Path root; private final LockManager lockManager; private final VolumeInformation volumeInformation; - private final CompletableFuture didMount; private final OpenHandleFactory fac; private final FileStore fileStore; - public ReadWriteAdapter(Path root, LockManager lockManager, VolumeInformation volumeInformation, CompletableFuture didMount) { + public ReadWriteAdapter(Path root, LockManager lockManager, VolumeInformation volumeInformation) { this.root = root; this.lockManager = lockManager; this.volumeInformation = volumeInformation; - this.didMount = didMount; this.fac = new OpenHandleFactory(); try { this.fileStore = Files.getFileStore(root); @@ -91,14 +89,12 @@ public ReadWriteAdapter(Path root, LockManager lockManager, VolumeInformation vo * @param fileSystemRoot * @param lockManager * @param volumeInfo - * @param mountDidSucceed * @param handleCheckBuilder */ - public ReadWriteAdapter(Path fileSystemRoot, LockManager lockManager, VolumeInformation volumeInfo, CompletableFuture mountDidSucceed, OpenHandleCheck.OpenHandleCheckBuilder handleCheckBuilder) { + public ReadWriteAdapter(Path fileSystemRoot, LockManager lockManager, VolumeInformation volumeInfo, OpenHandleCheck.OpenHandleCheckBuilder handleCheckBuilder) { this.root = fileSystemRoot; this.lockManager = lockManager; this.volumeInformation = volumeInfo; - this.didMount = mountDidSucceed; this.fac = new OpenHandleFactory(); try { this.fileStore = Files.getFileStore(root); @@ -835,7 +831,6 @@ public int getVolumeInformation(Pointer rawVolumeNameBuffer, int rawVolumeNameSi @Override public int mounted(DokanyFileInfo dokanyFileInfo) { LOG.trace("mounted() is called."); - didMount.complete(null); return 0; } diff --git a/src/test/java/org/cryptomator/frontend/dokany/MirrorReadOnlyThread.java b/src/test/java/org/cryptomator/frontend/dokany/MirrorReadOnlyThread.java index ec84c58..e474c0d 100644 --- a/src/test/java/org/cryptomator/frontend/dokany/MirrorReadOnlyThread.java +++ b/src/test/java/org/cryptomator/frontend/dokany/MirrorReadOnlyThread.java @@ -43,7 +43,7 @@ public MirrorReadOnlyThread(Path dirToMirror, Path mountPoint) { VolumeInformation volumeInfo = new VolumeInformation(VolumeInformation.DEFAULT_MAX_COMPONENT_LENGTH, "Mirror", 0x98765432, "Dokany MirrorFS", fsFeatures); - DokanyFileSystem myFs = new ReadWriteAdapter(dirToMirror, new LockManager(), volumeInfo, new CompletableFuture()); + DokanyFileSystem myFs = new ReadWriteAdapter(dirToMirror, new LockManager(), volumeInfo); dokany = new DokanyMount(devOps, myFs); } diff --git a/src/test/java/org/cryptomator/frontend/dokany/MultipleMirrorsTest.java b/src/test/java/org/cryptomator/frontend/dokany/MultipleMirrorsTest.java index b070d80..638b84d 100644 --- a/src/test/java/org/cryptomator/frontend/dokany/MultipleMirrorsTest.java +++ b/src/test/java/org/cryptomator/frontend/dokany/MultipleMirrorsTest.java @@ -29,8 +29,6 @@ public class MultipleMirrorsTest { private static String testDirPrefix = "testDir"; private ConcurrentLinkedQueue mounts; - private MountFactory mountFactory; - private ExecutorService mountThreads; @BeforeAll public static void beforeAll() { @@ -53,8 +51,6 @@ public static void beforeAll() { @BeforeEach public void init() throws IOException { this.mounts = new ConcurrentLinkedQueue<>(); - this.mountThreads = Executors.newCachedThreadPool(); - this.mountFactory = new MountFactory(mountThreads); } @@ -66,8 +62,7 @@ public void testMultipleConcurrentMirrorsFolderMounts() { var testDir = SANDBOX.resolve(testDirPrefix + i); var mountPoint = SANDBOX.resolve("mnt" + i); try { - MountFactory mountFactory = new MountFactory(mountThreads); - var mount = mountFactory.mount(testDir, mountPoint, "mnt" + i, "Mirror FS"); + var mount = MountFactory.mount(testDir, mountPoint, "mnt" + i, "Mirror FS"); mounts.add(mount); } catch (DokanyMountFailedException e) { e.printStackTrace(); @@ -85,8 +80,7 @@ public void testMultipleConcurrentMirrorsDriveLetterMounts() { char driveLetter = Character.toChars(0x4a + i)[0]; var mountPoint = Path.of(driveLetter + ":\\"); try { - MountFactory mountFactory = new MountFactory(mountThreads); - var mount = mountFactory.mount(testDir, mountPoint, "mnt" + i, "Mirror FS"); + var mount = MountFactory.mount(testDir, mountPoint, "mnt" + i, "Mirror FS"); mounts.add(mount); } catch (DokanyMountFailedException e) { e.printStackTrace(); diff --git a/src/test/java/org/cryptomator/frontend/dokany/ReadWriteCryptoFsTest.java b/src/test/java/org/cryptomator/frontend/dokany/ReadWriteCryptoFsTest.java index 55bcf2c..da6394d 100644 --- a/src/test/java/org/cryptomator/frontend/dokany/ReadWriteCryptoFsTest.java +++ b/src/test/java/org/cryptomator/frontend/dokany/ReadWriteCryptoFsTest.java @@ -53,8 +53,7 @@ public static void main(String[] args) throws IOException, DokanyMountFailedExce .build(); CryptoFileSystem cryptofs = CryptoFileSystemProvider.newFileSystem(vaultPath, props); Path path = cryptofs.getPath("/"); - MountFactory mountFactory = new MountFactory(Executors.newCachedThreadPool()); - try (Mount mount = mountFactory.mount(path, mountPoint, "MyVault", "CryptoFS")) { + try (Mount mount = MountFactory.mount(path, mountPoint, "MyVault", "CryptoFS")) { try { mount.reveal(new WindowsExplorerRevealer()); } catch (Exception e) { diff --git a/src/test/java/org/cryptomator/frontend/dokany/ReadWriteMirrorTest.java b/src/test/java/org/cryptomator/frontend/dokany/ReadWriteMirrorTest.java index 8d1bff1..932072a 100644 --- a/src/test/java/org/cryptomator/frontend/dokany/ReadWriteMirrorTest.java +++ b/src/test/java/org/cryptomator/frontend/dokany/ReadWriteMirrorTest.java @@ -6,7 +6,9 @@ import java.nio.file.Path; import java.util.Optional; import java.util.Scanner; -import java.util.concurrent.Executors; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; public class ReadWriteMirrorTest { @@ -17,7 +19,7 @@ public class ReadWriteMirrorTest { System.setProperty(SimpleLogger.DATE_TIME_FORMAT_KEY, "HH:mm:ss:SSS"); } - public static void main(String[] args) throws IOException, DokanyMountFailedException { + public static void main(String[] args) throws IOException, DokanyMountFailedException, InterruptedException { if (!MountFactory.isApplicable()) { System.err.println("Dokany not installed."); return; @@ -39,9 +41,9 @@ public static void main(String[] args) throws IOException, DokanyMountFailedExce } } - MountFactory mountFactory = new MountFactory(Executors.newSingleThreadExecutor()); - Runnable afterUnmountAction = () -> System.out.println("Volume unmounted"); - try (Mount mount = mountFactory.mount(dirPath, mountPoint, "Test", "DokanyNioFS", "--thread-count 5", afterUnmountAction)) { + CountDownLatch exitSignal = new CountDownLatch(1); + Consumer onDokanExitAction = exception -> exitSignal.countDown(); + try (Mount mount = MountFactory.mount(dirPath, mountPoint, "Test", "DokanyNioFS", "--thread-count 5", onDokanExitAction)) { try { mount.reveal(new WindowsExplorerRevealer()); } catch (Exception e) { @@ -50,7 +52,15 @@ public static void main(String[] args) throws IOException, DokanyMountFailedExce } System.in.read(); mount.unmountForced(); + + } finally { + if (exitSignal.await(3000, TimeUnit.MILLISECONDS)) { + System.out.println("onExit action executed."); + } else { + System.out.println("onExit action NOT executed after 3s. Exiting..."); + } } + } } From f02686b6045165db5bf74139de7dfb679c5dae8a Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 31 Mar 2021 09:30:38 +0200 Subject: [PATCH 14/17] simplify native thread enumeration by using own CallbackThreadInitializer --- .../dokany/java/DokanyOperationsProxy.java | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/dokany/java/DokanyOperationsProxy.java b/src/main/java/com/dokany/java/DokanyOperationsProxy.java index 7b855ed..d657346 100644 --- a/src/main/java/com/dokany/java/DokanyOperationsProxy.java +++ b/src/main/java/com/dokany/java/DokanyOperationsProxy.java @@ -30,18 +30,13 @@ final class DokanyOperationsProxy extends com.dokany.java.DokanyOperations { private final CountDownLatch mountSuccessSignaler; private final DokanyFileSystem fileSystem; - private final String nativeThreadNamePrefix; - private final ThreadGroup nativeThreadGroup; private final CallbackThreadInitializer callbackThreadInitializer; - private final AtomicInteger threadCounter = new AtomicInteger(0); private final List usedCallbacks = new ArrayList<>(); DokanyOperationsProxy(CountDownLatch mountSuccessSignaler, final DokanyFileSystem fileSystem, String nativeThreadNamePrefix) { this.mountSuccessSignaler = mountSuccessSignaler; this.fileSystem = fileSystem; - this.nativeThreadNamePrefix = nativeThreadNamePrefix; - this.nativeThreadGroup = new ThreadGroup(nativeThreadNamePrefix); - this.callbackThreadInitializer = new CallbackThreadInitializer(true, false, nativeThreadNamePrefix, nativeThreadGroup); + this.callbackThreadInitializer = new DokanCallbackThreadInitializer(nativeThreadNamePrefix); super.ZwCreateFile = new ZwCreateFileProxy(); usedCallbacks.add(super.ZwCreateFile); @@ -125,11 +120,6 @@ class ZwCreateFileProxy implements ZwCreateFile { @Override public long callback(WString rawPath, WinBase.SECURITY_ATTRIBUTES securityContext, int rawDesiredAccess, int rawFileAttributes, int rawShareAccess, int rawCreateDisposition, int rawCreateOptions, DokanyFileInfo dokanyFileInfo) { try { - //hack to uniquely identify the dokan threads - if (Thread.currentThread().getName().equals(nativeThreadNamePrefix)) { - Thread.currentThread().setName(nativeThreadNamePrefix + threadCounter.getAndIncrement()); - } - int win32ErrorCode = fileSystem.zwCreateFile(rawPath, securityContext, rawDesiredAccess, rawFileAttributes, rawShareAccess, rawCreateDisposition, rawCreateOptions, dokanyFileInfo); //little cheat for issue #24 if (win32ErrorCode == Win32ErrorCode.ERROR_INVALID_STATE.getMask()) { @@ -439,4 +429,21 @@ public long callback(WString rawPath, FillWin32FindStreamData rawFillFindData, D } } + private static class DokanCallbackThreadInitializer extends CallbackThreadInitializer { + + private String prefix; + private AtomicInteger counter; + + DokanCallbackThreadInitializer(String prefix) { + super(true, false, prefix, new ThreadGroup(prefix)); + this.prefix = prefix; + this.counter = new AtomicInteger(0); + } + + @Override + public String getName(Callback cb) { + return prefix + counter.getAndIncrement(); + } + } + } \ No newline at end of file From 4dac384dadb62c845b2a7b46481bfd97542cf619 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 31 Mar 2021 09:35:49 +0200 Subject: [PATCH 15/17] use proper case for static final var --- src/main/java/com/dokany/java/DokanyMount.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/dokany/java/DokanyMount.java b/src/main/java/com/dokany/java/DokanyMount.java index 7fd2686..88b40af 100644 --- a/src/main/java/com/dokany/java/DokanyMount.java +++ b/src/main/java/com/dokany/java/DokanyMount.java @@ -23,7 +23,7 @@ public final class DokanyMount implements Mount { private static final Logger LOG = LoggerFactory.getLogger(DokanyMount.class); private static final int MOUNT_TIMEOUT_MILLIS = 3000; - private static final AtomicInteger mountCounter = new AtomicInteger(1); + private static final AtomicInteger MOUNT_COUNTER = new AtomicInteger(1); private final DeviceOptions deviceOptions; private final DokanyFileSystem fileSystem; @@ -82,7 +82,7 @@ public void mount() throws DokanyException { */ public synchronized void mount(Consumer onDokanExit) throws DokanyException { if (!isMounted.getAndSet(true)) { - int mountId = mountCounter.getAndIncrement(); + int mountId = MOUNT_COUNTER.getAndIncrement(); try { Runtime.getRuntime().addShutdownHook(new Thread(this::close)); From 949584790918eb632c9686fe187b1b1202a7564d Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 13 Apr 2021 11:50:47 +0200 Subject: [PATCH 16/17] Return for getFileSecurity() NOT_IMPLEMENTED to let Dokany build a security descriptor --- .../java/com/dokany/java/DokanyOperationsProxy.java | 4 ++-- .../cryptomator/frontend/dokany/ReadWriteAdapter.java | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/dokany/java/DokanyOperationsProxy.java b/src/main/java/com/dokany/java/DokanyOperationsProxy.java index d657346..1a2eee7 100644 --- a/src/main/java/com/dokany/java/DokanyOperationsProxy.java +++ b/src/main/java/com/dokany/java/DokanyOperationsProxy.java @@ -103,8 +103,8 @@ final class DokanyOperationsProxy extends com.dokany.java.DokanyOperations { super.Unmounted = new UnmountedProxy(); usedCallbacks.add(super.Unmounted); - super.GetFileSecurity = null; - //callbacks.add(super.GetFileSecurity); + super.GetFileSecurity = ((rawPath, rawSecurityInformation, rawSecurityDescriptor, rawSecurityDescriptorLength, rawSecurityDescriptorLengthNeeded, dokanyFileInfo) -> NtStatus.NOT_IMPLEMENTED.getMask()); + usedCallbacks.add(super.GetFileSecurity); super.SetFileSecurity = null; //callbacks.add(super.SetFileSecurity); diff --git a/src/main/java/org/cryptomator/frontend/dokany/ReadWriteAdapter.java b/src/main/java/org/cryptomator/frontend/dokany/ReadWriteAdapter.java index de18bc5..84fe843 100644 --- a/src/main/java/org/cryptomator/frontend/dokany/ReadWriteAdapter.java +++ b/src/main/java/org/cryptomator/frontend/dokany/ReadWriteAdapter.java @@ -845,6 +845,16 @@ public int unmounted(DokanyFileInfo dokanyFileInfo) { return 0; } + /** + * Not implemented, handled in proxy. + * @param rawPath + * @param rawSecurityInformation + * @param rawSecurityDescriptor + * @param rawSecurityDescriptorLength + * @param rawSecurityDescriptorLengthNeeded + * @param dokanyFileInfo {@link DokanyFileInfo} with information about the file or directory. + * @return + */ @Override public int getFileSecurity(WString rawPath, int rawSecurityInformation, Pointer rawSecurityDescriptor, int rawSecurityDescriptorLength, IntByReference rawSecurityDescriptorLengthNeeded, DokanyFileInfo dokanyFileInfo) { // Path path = getRootedPath(rawPath); From 04011a8227c1c267c7ff3d6e4286f6508fd2f1f5 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 15 Apr 2021 10:17:55 +0200 Subject: [PATCH 17/17] preparing 1.3.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f5bec6c..5df084e 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.cryptomator dokany-nio-adapter - 1.3.0-SNAPSHOT + 1.3.0 Access resources at a given NIO path via Dokany. Dokany-NIO Adapter https://github.com/cryptomator/dokany-nio-adapter